When function shapes don't match
You're writing a retry helper that takes a callback. You define a function to fetch data, pass it to the helper, and the compiler rejects the code. You look at the signatures. Both functions take no arguments. Both return an error. The error message points at the call site and says the types don't match. The mismatch is often subtle. It could be an extra return value you forgot to discard, a missing context parameter, or a method value that hasn't been bound to a receiver. Go treats function types strictly. The signature must align perfectly with the expected type. No implicit conversion exists to bridge the gap.
Function types are strict blueprints
In Go, a function type describes the shape of a function. It specifies the input parameters and the output values. When you declare a variable with a function type, you create a container for a function value. The container has a fixed shape determined by the type declaration. You can only store a function value in that container if its shape matches exactly.
This strictness prevents runtime errors. If the compiler allowed mismatched functions, you could accidentally call a function with the wrong number of arguments or ignore a critical return value. The error cannot use X as type func() appears when you try to assign or pass a function value to a variable or parameter with a different function type. The compiler compares the signatures and stops the program if any part differs.
Parameter names do not affect type identity. A function declared as func(x int) int has the same type as func(y int) int. The compiler ignores names when comparing signatures. This allows you to rename parameters for clarity without breaking type compatibility. Named return values also do not change the type. func foo() (int, error) and func bar() (result int, err error) share the same type. The type depends only on the types of the parameters and return values.
Function types enforce explicit intent. If a function signature differs from what the caller expects, you must write an adapter. This makes the transformation visible in the code.
Signatures must match. Write the adapter.
Minimal example: missing return value
The most common cause of this error is a mismatch in return values. You might define a function that returns nothing, but the caller expects a return value. Or you might return an error when the caller expects only a success value.
package main
import "fmt"
// Apply runs a transformation function on a value and prints the result.
// The parameter 'fn' must be a function that takes an int and returns an int.
func Apply(fn func(int) int, val int) {
res := fn(val)
fmt.Println(res)
}
func main() {
// square matches the expected signature: func(int) int.
// It takes an int and returns an int.
square := func(x int) int { return x * x }
Apply(square, 4)
// logSquare prints the square but returns nothing.
// Its type is func(int), which lacks the return value.
logSquare := func(x int) { fmt.Println(x * x) }
// The compiler rejects this call.
// Error: cannot use logSquare (variable of type func(int)) as func(int) int value in argument
// Apply(logSquare, 4)
}
The compiler checks the type of logSquare. It sees func(int). It checks the type of the fn parameter in Apply. It sees func(int) int. The parameter list matches: both take one int. The return list does not. The expected type returns an int. The provided type returns nothing. The mismatch triggers the error. The error message tells you exactly what you provided and what was expected. It says cannot use logSquare (variable of type func(int)) as func(int) int value in argument.
The fix is to make the signatures match. Either change the function to return the value, or change the caller to accept a function with no return value. In this case, logSquare doesn't return a value, so Apply cannot use it. You would need a different helper that accepts func(int).
The compiler protects you from calling functions with the wrong shape.
Realistic example: adapting return values
In real code, you often encounter functions that return more values than a utility function needs. A common scenario is a retry mechanism. The retry function expects a callback that returns an error. Your actual function might return data and an error. You can't pass the function directly. You need an adapter.
package main
import (
"fmt"
"time"
)
// Retry executes a function repeatedly until it succeeds or maxAttempts is reached.
// The function must return an error; nil means success.
func Retry(fn func() error, maxAttempts int) error {
var lastErr error
for i := 0; i < maxAttempts; i++ {
// Call the function and capture the error.
lastErr = fn()
if lastErr == nil {
return nil
}
// Back off before retrying.
time.Sleep(100 * time.Millisecond)
}
return lastErr
}
// fetchConfig retrieves configuration from a remote service.
// It returns the config string and an error.
func fetchConfig() (string, error) {
return "config", nil
}
func main() {
// fetchConfig has type func() (string, error).
// Retry expects func() error.
// The return types differ: one returns a string and error, the other only error.
// Error: cannot use fetchConfig (value of type func() (string, error)) as func() error value in argument
// Retry(fetchConfig, 3)
// Create an adapter function that matches Retry's expectation.
// The adapter calls fetchConfig and ignores the string result.
// This is a common pattern when the utility function needs a simpler signature.
retryableFetch := func() error {
_, err := fetchConfig()
return err
}
Retry(retryableFetch, 3)
}
The adapter function retryableFetch has the type func() error. It calls fetchConfig, discards the string using the underscore, and returns the error. The underscore signals that you considered the value and chose to drop it. This is a Go convention. Use it sparingly with errors, but it's appropriate here because the retry logic only cares about success or failure.
The adapter bridges the gap between the complex function and the simple utility. It makes the transformation explicit. Readers can see exactly how the values are handled.
Adapters make intent clear. Don't hide conversions.
Common pitfalls and compiler errors
Several patterns trigger this error beyond simple return mismatches. Understanding these helps you spot the issue quickly.
Missing context parameter
Many Go functions require context.Context as the first argument. If you define a callback without context and pass it to a function expecting context, the compiler rejects it. The error highlights the missing parameter.
// Expected: func(ctx context.Context) error
// Provided: func() error
// Error: cannot use callback (variable of type func() error) as func(context.Context) error value in argument
Always check if the target signature includes context. If the utility function passes context to the callback, your callback must accept it.
Method values vs method expressions
When passing methods, you might confuse method values with method expressions. A method value binds the receiver to a specific instance. A method expression does not.
type Server struct {
addr string
}
// Start begins listening on the server's address.
// It returns an error if the address is invalid.
func (s *Server) Start() error {
if s.addr == "" {
return fmt.Errorf("address required")
}
return nil
}
func main() {
s := &Server{addr: ":8080"}
// s.Start is a method value.
// It has type func() error.
// The receiver is bound to s.
Retry(s.Start, 3)
// Server.Start is a method expression.
// It has type func(*Server) error.
// It requires the receiver as an argument.
// Error: cannot use Server.Start (value of type func(*Server) error) as func() error value in argument
// Retry(Server.Start, 3)
}
Use s.Start to pass a bound method. Use Server.Start when you need to pass the receiver explicitly. The compiler error tells you the difference. func() error vs func(*Server) error.
HTTP handlers
The standard library uses http.HandlerFunc as a type adapter. It's a type alias for func(http.ResponseWriter, *http.Request). If you write a handler that returns an error, you can't pass it directly to http.ListenAndServe. You need to convert it.
// handler returns an error, which doesn't match http.HandlerFunc.
func handler(w http.ResponseWriter, r *http.Request) error {
return nil
}
// Error: cannot use handler (value of type func(http.ResponseWriter, *http.Request) error) as http.HandlerFunc value in argument
// http.ListenAndServe(":8080", handler)
// Wrap the handler to match the signature.
http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := handler(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}))
The wrapper calls your handler and handles the error. This pattern is standard in Go web development. The convention is to accept interfaces and return structs, but for HTTP handlers, the adapter type is the bridge.
Check the return types. Named returns don't hide the shape.
Decision matrix: handling function mismatches
Use a direct function assignment when the function signature matches the expected type exactly, including all parameters and return values.
Use an adapter function when the target function returns additional values that the caller ignores, wrapping the call to discard the extra results.
Use a wrapper function when you need to transform arguments or inject dependencies before invoking the underlying function.
Use a method value when you need to pass a method bound to a specific receiver instance, ensuring the method has access to the struct's state.
Use the http.HandlerFunc adapter type when registering a handler function with http.ListenAndServe or http.ServeMux, as the standard library expects this specific type.
Use a closure when you need to capture variables from the surrounding scope to customize the behavior of the passed function.
Match the signature or wrap the call.