Go
Functions & Multiple Return Values
C functions return exactly one value — anything more requires an output pointer parameter. Go functions can return more than one value directly, no pointer trick required — a small syntax feature that turns out to be the foundation of how Go handles errors everywhere in the language.
- What it is
- Go functions can declare and return more than one value in a single
returnstatement. - Mental model
- Where C smuggles a second output value out through a pointer parameter, Go just lets a function hand back two (or more) values directly, as first-class citizens of the return statement.
- Key numbers
- The overwhelmingly common pattern is exactly two return values:
(result, error)— covered in full in the upcoming Error Handling chapter. - Reach for it when
- A function's caller genuinely needs more than one piece of information back — most often a result plus whether something went wrong.
Functions, the familiar parts
func add(a int, b int) int {
return a + b
}
func multiply(a, b int) int { // same type back-to-back: the type is written once
return a * b
}
result := add(3, 4) // 7
Structurally the same idea as C functions — a name, parameters, a return type, a body — with one small syntax difference: the return type comes after the parameter list, not before the function name.
Multiple return values
func divide(a, b float64) (float64, bool) { // returns TWO values: the result, and whether it succeeded
if b == 0 {
return 0, false
}
return a / b, true
}
result, ok := divide(10, 2)
if ok {
fmt.Println(result) // 5
}
result2, ok2 := divide(10, 0)
if !ok2 {
fmt.Println("division failed") // division failed
}
Compare this to how the equivalent would look in C — an output pointer parameter, and the return value repurposed as a success/failure flag:
// C: the pointer-based equivalent
int divide(float a, float b, float *result) {
if (b == 0) return 0; // 0 = failure
*result = a / b;
return 1; // 1 = success
}
Go's version needs no address-of operator at the call site, no dereferencing inside the function, and no risk of a caller forgetting to pass a valid pointer — the two return values are simply there.
Named return values
A function can name its return values in the signature — they behave like pre-declared local variables,
and a bare return sends back whatever they currently hold:
func divide(a, b float64) (result float64, ok bool) {
if b == 0 {
return // returns the zero values: 0, false
}
result = a / b
ok = true
return // returns result and ok, whatever they currently are
}
Useful for self-documenting what each return value means, but easy to overuse — most Go style guides recommend named returns only when they genuinely clarify the function, not as a default habit.
Variadic functions
A function can accept a variable number of arguments of the same type — Go's equivalent of the concept
behind C's printf-style variadic functions, but type-safe:
func sum(numbers ...int) int { // accepts any number of ints
total := 0
for _, n := range numbers {
total += n
}
return total
}
sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5) // 15
sum() // 0 — zero arguments is valid too
Real-world applications
- Every I/O operation in Go's standard library — file reads, network calls, and parsing functions almost universally return
(value, error)— the pattern the upcoming Error Handling chapter covers in full. - Map lookups — Go's built-in map access itself returns two values: the value, and whether the key actually existed — directly reusing this same language feature.
- Type assertions — converting an interface back to a concrete type (covered in the Interfaces chapter) also returns a value plus a success flag, the same shape as the
divideexample above.
Gotchas
- Ignoring a returned error (or success flag) is a common real bug — Go doesn't force you to check it, so silently discarding
divide(10, 0)'s second return value re-introduces exactly the "unchecked error code" problem C has, just with nicer syntax. - The blank identifier
_explicitly discards a return value you genuinely don't need (as used for the index in thesumexample'sfor _, n := range numbers) — a deliberate choice, different from silently ignoring one. - Named return values combined with an early bare
returncan make it easy to lose track of exactly what's being returned at each exit point — if a function has several early returns, consider being explicit (return result, ok) rather than relying on the bare form everywhere. - A variadic parameter must be the last parameter in a function's signature — Go enforces this at compile time, the same restriction C's own variadic functions have.