Question

Go Method Overloading: Why SetOption Is Redeclared

gooverloading

Question

I am porting a C library to Go. The C API contains a variadic function:

curl_easy_setopt(CURL *curl, CURLoption option, ...);

To call it safely from Go through cgo, I created fixed-argument C wrapper functions:

curl_wrapper_easy_setopt_str(CURL *curl, CURLoption option, char *param);
curl_wrapper_easy_setopt_long(CURL *curl, CURLoption option, long param);

I then tried to expose both wrappers through Go methods with the same name but different parameter types:

func (e *Easy) SetOption(option Option, param string) {
    e.code = Code(C.curl_wrapper_easy_setopt_str(
        e.curl,
        C.CURLoption(option),
        C.CString(param),
    ))
}

func (e *Easy) SetOption(option Option, param long) {
    e.code = Code(C.curl_wrapper_easy_setopt_long(
        e.curl,
        C.CURLoption(option),
        C.long(param),
    ))
}

The Go compiler reports:

Easy.SetOption redeclared in this block

Does Go support function or method overloading, or does this error indicate a different problem?

Short Answer

Go does not support function or method overloading. A package cannot contain two functions, or two methods on the same receiver type, with the same name—even if their parameter types differ. For cgo wrappers around C variadic functions, use distinct Go method names or design one explicitly typed option API.

Concept

In languages with overloading, multiple functions can share a name when their parameter lists differ. For example, C++ can select a different setOption based on whether an argument is a string or an integer.

Go deliberately does not have this feature. A function is identified by its name within a package, and a method is identified by its receiver type and method name. Parameter types and return types are not used to distinguish declarations.

Therefore, these conflict:

func (e *Easy) SetOption(option Option, param string) {}
func (e *Easy) SetOption(option Option, param int64)  {}

Both declarations define a method named SetOption on *Easy, so the second declaration causes a redeclaration error.

This matters especially when wrapping C APIs. C libraries may use varargs (...) to accept several kinds of values, but cgo cannot directly call C variadic functions in the general case. The usual solution is to write fixed-signature C wrappers, then expose a clear Go API that selects the correct wrapper.

Mental Model

Think of a Go method name as a labeled drawer on a cabinet.

A receiver type such as *Easy may have one drawer labeled SetOption. Go does not inspect the contents you put into the drawer—such as a string or an int64—to decide which drawer you meant. If you create another drawer with the same SetOption label, Go reports a conflict.

Use distinct labels such as SetOptionString and SetOptionLong, or use one drawer that accepts a deliberately designed option value.

Take Quiz

Syntax and Examples

A Go method declaration has this shape:

func (receiver ReceiverType) MethodName(parameters) returnValues {
    // body
}

The receiver type and method name must be unique. These methods are valid because their names differ:

type Easy struct {
    code Code
}

type Option int

type Code int

func (e *Easy) SetOptionString(option Option, value string) {
    // Call the C wrapper that expects a char *.
}

func (e *Easy) SetOptionLong(option Option, value int64) {
    // Call the C wrapper that expects a C long.
}

Usage is explicit:

easy.SetOptionString(URL, "https://example.com")
easy.SetOptionLong(Timeout, 30)

Use a Go numeric type such as int64, int, or a named type—not long. long is a C type name, not a built-in Go type. In cgo code, the C type is written as C.long when converting an argument for a C function.

Step by Step Execution

Consider this valid code:

type Easy struct{}
type Option int

func (e *Easy) SetOptionString(option Option, value string) {
    fmt.Println("string:", value)
}

func (e *Easy) SetOptionLong(option Option, value int64) {
    fmt.Println("long:", value)
}

func main() {
    easy := &Easy{}
    easy.SetOptionString(1, "https://example.com")
    easy.SetOptionLong(2, 10)
}

Execution proceeds as follows:

  1. easy points to an Easy value.
  2. SetOptionString is called by its exact name. Go passes "https://example.com" as a string.
  3. SetOptionLong is called by its exact name. Go passes 10 as an int64.
  4. No type-based method selection is needed because each method has a unique name.

By contrast, if both methods were called SetOption, Go would reject the program before it could run. The compiler sees duplicate declarations while building the method set for *Easy.

Real World Use Cases

Distinct methods are useful when different argument types represent genuinely different operations:

  • cgo wrappers: SetOptionString, SetOptionLong, and SetOptionPointer can call C functions with fixed, safe signatures.
  • Configuration APIs: SetTimeout(time.Duration) and SetHeader(name, value string) communicate what each setting expects.
  • Database bindings: separate methods may bind text, integer, byte, and null values to a statement.
  • File and network APIs: WriteString, Write, and WriteAt use distinct names to describe both data and behavior.
  • Validation: SetEmail, SetPort, and SetRetryLimit can validate each domain-specific value before storing it.

Clear names are often easier to read than one overloaded name whose behavior must be inferred from argument types.

Take Quiz

Real Codebase Usage

For a C varargs API, a common approach is to keep the unsafe or cgo-specific details in a small internal layer and expose typed Go methods.

func (e *Easy) SetURL(url string) error {
    cURL := C.CString(url)
    defer C.free(unsafe.Pointer(cURL))

    code := C.curl_wrapper_easy_setopt_str(
        e.curl,
        C.CURLoption(C.CURLOPT_URL),
        cURL,
    )
    return codeToError(code)
}

func (e *Easy) SetTimeout(seconds int64) error {
    if seconds < 0 {
        return errors.New("timeout must not be negative")
    }

    code := C.curl_wrapper_easy_setopt_long(
        e.curl,
        C.CURLoption(C.CURLOPT_TIMEOUT),
        C.long(seconds),
    )
    return codeToError(code)
}

This pattern provides several benefits:

  • Callers use meaningful methods rather than manually matching options to value types.
  • Validation is near the API boundary.
  • defer C.free(...) prevents a temporary C.CString allocation from leaking.
  • C-specific types such as C.long stay inside the wrapper implementation.
  • Error conversion gives Go callers normal Go error handling.

If a generic option method is necessary, use an explicit type switch and return an error for unsupported types. However, this trades away some compile-time checking.

Common Mistakes

Assuming different parameter types permit duplicate method names

This is invalid in Go:

func (e *Easy) SetOption(option Option, value string) {}
func (e *Easy) SetOption(option Option, value int64) {}

Avoid it: give the methods distinct names, such as SetOptionString and SetOptionLong.

Using long as a Go type

This is invalid unless you declared your own type named long:

func (e *Easy) SetOption(option Option, value long) {}

Avoid it: use Go types such as int64. Convert to C.long only at the cgo boundary:

C.long(value)

Leaking memory from C.CString

C.CString allocates C-owned memory. This leaks if it is not freed:

cValue := C.CString(value)
C.curl_wrapper_easy_setopt_str(e.curl, option, cValue)

Avoid it: free the string after the C call when the C library does not retain the pointer:

cValue := C.CString(value)
defer C.free(unsafe.Pointer(cValue))

If the C library retains the pointer, freeing it immediately is incorrect. The Go wrapper must instead keep an appropriate C allocation alive for as long as the library needs it.

Passing arbitrary values through any without checks

This compiles but can fail at runtime:

func (e *Easy) SetOption(option Option, value any) {
    // No safe way to call the correct C wrapper without checking value.
}

Avoid it: prefer typed methods. If using any, handle every supported type in a type switch and reject everything else.

Assuming all C numeric types match Go numeric types

The size of C long is platform-dependent. int64 may not always match it exactly.

Avoid it: verify the C API's required range, validate before conversion when needed, and use C.long at the boundary.

Comparisons

ApproachValid in Go?Best whenTrade-off
Same method name with different parameter typesNoNever; this is method overloadingGo reports a redeclaration error
Distinct typed methodsYesThe option/value combinations are knownMore method names, but strong compile-time checking
One method with any and a type switchYesA small dynamic compatibility layer is neededErrors move from compile time to runtime
Typed option valuesYesBuilding a Go-first configuration APIRequires designing option types

A single dynamic method can look like this:

func (e *Easy) SetOption(option Option, value any) error {
    switch v := value.(type) {
    case string:
        // Call the string C wrapper with v.
        return nil
    case int64:
        // Call the long C wrapper with v.
        return nil
    default:
        return fmt.Errorf("unsupported option value type %T", value)
    }
}

This is not overloading. It is one method whose body checks the runtime type of value.

For most public Go APIs, named methods or domain-specific methods are clearer than any.

Take Quiz

Cheat Sheet

  • Go has no function overloading and no method overloading.

  • Two methods on the same receiver cannot share a name, regardless of parameter types or return types.

  • Use distinct names:

    func (e *Easy) SetOptionString(option Option, value string)
    func (e *Easy) SetOptionLong(option Option, value int64)
    
  • long is not a built-in Go type. Use int64, int, or a named Go type.

  • In cgo, convert at the boundary with types such as C.long.

  • cgo generally cannot call C variadic functions directly; use fixed-signature C wrappers.

  • C.CString allocates memory. Call C.free when the C API does not retain the string pointer.

  • A method taking any plus a type switch is possible, but it is runtime dispatch—not overloading.

FAQ

Does Go support function overloading?

No. You cannot declare two package-level functions with the same name, even if their parameters differ.

Does Go support method overloading?

No. A receiver type can have only one method with a given name. Parameter and result types do not make another declaration distinct.

Why does Go avoid method overloading?

Go favors simple, explicit APIs and straightforward name lookup. Callers can see the operation from the method name instead of relying on argument-type selection.

Can I use interface{} or any to imitate overloading?

Yes, with a type switch, but it is one method and type checking happens at runtime. Typed methods are usually safer and clearer.

Can cgo call a C function with ... varargs directly?

Generally, no. Write C wrapper functions with fixed parameter types, then call those wrappers from Go.

Is long a valid Go type?

No. It is a C type name. Use a Go numeric type such as int64, then convert to C.long when calling C code.

Should I always free C.CString immediately?

Free it after the C call only if the C function copies or consumes the string during that call. If the C library stores the pointer, the allocation must remain valid until the library no longer uses it.

Take Quiz

Mini Project

Description

Build a small Go-facing wrapper design for a C-style settings API. The project demonstrates how to replace an overloaded C varargs interface with explicit, type-safe Go methods.

Goal

Create an Easy type that stores string and integer options through separate methods and rejects invalid integer values.

Requirements

  • Define an Option type and constants for a URL and timeout option.
  • Create an Easy type that stores a URL and timeout value.
  • Add SetOptionString for string-valued options.
  • Add SetOptionLong for integer-valued options.
  • Return an error when a method receives an option of the wrong kind.
  • Reject negative timeout values.
Take Quiz

Keep learning

Related questions

View more