Fix

"cannot convert X to type Y" in Go

Fix Go type conversion errors by explicitly casting values using the type(value) syntax.

When the compiler refuses to guess

You write a function that expects a float64. You pass an int. The compiler stops you dead with cannot convert x to type float64. If you come from Python or JavaScript, this feels like a trap. Those languages quietly promote types behind the scenes. Go refuses to guess. It wants you to state exactly what you are doing to the data.

Go treats type conversion like changing currency at a border crossing. You cannot just walk across with dollars and expect the local shop to accept them as euros. You have to go to a booth, declare the exchange, and get the new currency. The type(value) syntax is that booth. It tells the compiler, "I know these two types are different, and I explicitly want to transform this value into that type." The compiler checks if the conversion is mathematically or structurally possible. If it is, it generates the machine code to do the transformation. If it is not, compilation fails.

The minimal pattern

Every conversion in Go follows the exact same shape. You write the target type, open a parenthesis, place the value inside, and close the parenthesis. The compiler validates the operation at compile time.

// ConvertNumeric shows explicit conversion between basic types.
func ConvertNumeric() {
    // Start with an integer value.
    count := 42
    
    // Explicitly convert to float64 for decimal math.
    // The syntax TargetType(value) tells the compiler to perform the transformation.
    precise := float64(count)
    
    // Converting back to int truncates the decimal portion.
    // Go does not round automatically. It drops everything after the decimal point.
    truncated := int(precise)
    
    _ = truncated
}

The syntax looks like a function call, but it is not. There is no function lookup, no stack frame, and no runtime overhead. It is a compile-time directive that changes how the compiler interprets the bits.

What happens under the hood

At compile time, the Go compiler checks two things. First, it verifies that the source type and target type are convertible according to the language specification. You can convert between numeric types, between string and []byte or []rune, and between a custom type and its underlying type. Second, it emits the conversion instruction directly into the binary.

For int to float64, the compiler generates a CPU instruction that moves the bits into a floating-point register and adjusts the exponent. For string to []byte, it generates code that copies the string data into a new slice header. The conversion is baked into the executable. At runtime, the value is already the correct type. There is no hidden pointer, no wrapper object, and no garbage collection pressure. The transformation happens exactly once, at the line where you wrote it.

Go prefers this explicitness because it makes data flow visible. The community accepts the extra keystrokes because they prevent silent data loss. When you read float64(count), you know exactly where the type changes and why.

Real-world usage: custom types and boundaries

The conversion rule becomes especially important when you define your own types. Go lets you create new types based on existing ones, but it treats them as completely separate identities. This prevents accidental mixing of values that happen to share the same underlying representation.

// UserID represents a unique user identifier in the system.
type UserID int

// ParseID converts a string from a URL query parameter into a UserID.
// It demonstrates converting between a custom type and its underlying type.
func ParseID(raw string) (UserID, error) {
    // Parse the string to an int first.
    // strconv handles the string-to-number conversion safely.
    val, err := strconv.Atoi(raw)
    if err != nil {
        return 0, err
    }
    
    // Even though UserID is just an int underneath, Go treats them as distinct types.
    // You must explicitly convert between them to cross the type boundary.
    return UserID(val), nil
}

This pattern is standard in Go. You define type Port uint16 or type Currency string to attach meaning to raw data. The compiler forces you to convert at the boundary where data enters or leaves your domain. You cannot accidentally pass a Port where a UserID is expected. The type system catches the mistake before the program runs.

The string and interface trap

Strings and interfaces are where beginners lose the most time. Go deliberately restricts direct conversions involving them to prevent subtle bugs.

You cannot convert a number directly to a string using string(42). That syntax actually converts the integer 42 to the ASCII character with code point 42, which is an asterisk. If you want the textual representation "42", you must use the strconv package. The convention is strict: use strconv.Itoa for integers, strconv.FormatFloat for decimals, and strconv.ParseInt or strconv.ParseFloat for the reverse. The standard library provides these functions because string formatting involves locale, base, and precision rules that a simple type conversion cannot handle.

Interfaces work differently. An interface value holds both a concrete type and a concrete value. You cannot convert an interface to a concrete type using TargetType(value). The compiler rejects it with cannot convert x (type interface{...}) to type string. Interfaces require a type assertion instead: value.(string). A type assertion checks the runtime type and panics if it does not match. A conversion is a compile-time guarantee. Mixing them up is the fastest way to trigger a runtime panic or a confusing build error.

Where the error actually comes from

The cannot convert x to type y error appears when the compiler's conversion rules reject the pair. It is not a runtime panic. It is a hard stop during the build phase. The most common triggers involve incompatible categories of data.

If you try to convert a pointer to its underlying value, the compiler rejects it with cannot convert x (type *int) to type int. Pointers and values live in different memory spaces. You need to dereference the pointer with *x, not convert it. If you attempt to turn a struct into a primitive number, you get cannot convert x (type struct{...}) to type int. The compiler has no rule for flattening a struct into a single integer. If you try to convert between two unrelated custom types, even if they share the same underlying type, you get cannot convert x (type A) to type B. Go requires you to convert through the underlying type: B(A(value)).

Beginners often confuse conversion with assignment compatibility. Assignment allows implicit matching when types are identical or when a value satisfies an interface. Conversion is strictly for changing the type itself. The compiler error message will always tell you exactly which types are involved and why the rule does not apply.

Read the error carefully. It names the source type and the target type. Match them against the conversion rules, and the fix is usually one line of explicit syntax.

Choosing the right tool

Go provides several mechanisms for handling type differences. Picking the wrong one causes confusing errors or runtime panics.

Use explicit conversion TargetType(value) when you need to change the underlying representation of a value, like turning an integer into a floating-point number for division. Use a type assertion value.(TargetType) when you have an interface value and need to extract the concrete type it currently holds. Use strconv functions when you need to convert between strings and numbers, since Go does not allow direct string(int) or int(string) conversions. Use a helper function or method when you need to convert between complex types like structs, because the compiler cannot guess how to map fields. Use plain assignment when the types already match exactly, or when a value satisfies an interface implicitly.

The compiler will not save you from logical mistakes, but it will force you to be honest about type boundaries. Write the conversion where the data crosses the line.

Where to go next