Skip to main content
โšก Calmops

Multiple Return Values in Go

Multiple Return Values in Go

Multiple return values are a distinctive feature of Go. They enable elegant error handling and returning multiple results without using pointers or structs.

Basic Multiple Returns

Returning Two Values

package main

import "fmt"

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

Returning Multiple Values

package main

import "fmt"

func getCoordinates() (float64, float64, string) {
    return 10.5, 20.3, "Point A"
}

func main() {
    x, y, label := getCoordinates()
    fmt.Printf("Point %s: (%.1f, %.1f)\n", label, x, y)
}

Named Return Values

Using Named Returns

package main

import "fmt"

func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = fmt.Errorf("division by zero")
        return
    }
    result = a / b
    return
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

Named Returns with Documentation

package main

import "fmt"

// Calculate returns the sum and product of two numbers
func calculate(a, b int) (sum int, product int) {
    sum = a + b
    product = a * b
    return
}

func main() {
    s, p := calculate(5, 3)
    fmt.Printf("Sum: %d, Product: %d\n", s, p)
}

Error Handling Patterns

The Error Pattern

package main

import (
    "fmt"
    "os"
)

func readFile(filename string) ([]byte, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("failed to read file: %w", err)
    }
    return data, nil
}

func main() {
    data, err := readFile("data.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Data:", string(data))
}

Multiple Error Returns

package main

import (
    "fmt"
    "strconv"
)

func parseInts(strs []string) ([]int, error) {
    result := make([]int, len(strs))
    for i, s := range strs {
        num, err := strconv.Atoi(s)
        if err != nil {
            return nil, fmt.Errorf("invalid number at index %d: %w", i, err)
        }
        result[i] = num
    }
    return result, nil
}

func main() {
    nums, err := parseInts([]string{"1", "2", "3"})
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Numbers:", nums)
    }
}

Ignoring Return Values

Using Blank Identifier

package main

import (
    "fmt"
    "os"
)

func main() {
    // Ignore error
    data, _ := os.ReadFile("file.txt")
    fmt.Println(string(data))

    // Ignore data
    _, err := os.ReadFile("file.txt")
    if err != nil {
        fmt.Println("Error:", err)
    }
}

Returning Structs

Multiple Values as Struct

package main

import "fmt"

type Result struct {
    Value int
    Error error
}

func calculate(a, b int) Result {
    if b == 0 {
        return Result{
            Value: 0,
            Error: fmt.Errorf("division by zero"),
        }
    }
    return Result{
        Value: a / b,
        Error: nil,
    }
}

func main() {
    result := calculate(10, 2)
    if result.Error != nil {
        fmt.Println("Error:", result.Error)
    } else {
        fmt.Println("Result:", result.Value)
    }
}

Variadic Returns

Returning Slices

package main

import "fmt"

func getNumbers() []int {
    return []int{1, 2, 3, 4, 5}
}

func main() {
    nums := getNumbers()
    fmt.Println(nums)
}

Best Practices

โœ… Good: Always Return Errors

// DO: Return errors explicitly
func process(data string) (string, error) {
    if data == "" {
        return "", fmt.Errorf("data cannot be empty")
    }
    return strings.ToUpper(data), nil
}

โŒ Bad: Ignore Errors

// DON'T: Ignore errors
func process(data string) string {
    // No error handling
    return strings.ToUpper(data)
}

โœ… Good: Use Named Returns for Clarity

// DO: Use named returns for clarity
func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = fmt.Errorf("division by zero")
        return
    }
    result = a / b
    return
}

โœ… Good: Wrap Errors with Context

// DO: Wrap errors with context
if err != nil {
    return fmt.Errorf("failed to process: %w", err)
}

โŒ Bad: Generic Error Messages

// DON'T: Use generic error messages
if err != nil {
    return fmt.Errorf("error")
}

Summary

Multiple return values provide:

  1. Error handling without exceptions
  2. Multiple results without structs
  3. Named returns for clarity
  4. Idiomatic Go patterns
  5. Explicit error propagation

These features make Go’s error handling explicit and intentional.

Comments