Go: cannot use x (variable of type T) as type U in assignment

Quick answer

Go never converts a value's type for you — every conversion must be written out. Match the cause to the fix:

  • Numeric types (int vs int64, int vs float64) — wrap it: int64(x).
  • string vs []byte — convert with []byte(s) or string(b).
  • Two named types sharing an underlying type — convert explicitly: Fahrenheit(c).
  • Message ends "does not implement" — different problem: the type is missing an interface method, or you need a pointer receiver.

The exact error string

package main

func main() {
    var x int = 10
    var y int64 = x
    _ = y
}

// ./main.go:5:15: cannot use x (variable of type int) as int64 value in assignment
//
// older Go toolchains (pre 1.18) phrased it as:
// ./main.go:5:15: cannot use x (type int) as type int64 in assignment

The tail changes with context but the shape stays the same: in assignment becomes in argument to f when it's a function call, and in return statement when a return doesn't match the declared return type. All three come from the same rule and take the same fix.

When the target is an interface rather than a concrete type, the message gets an extra line naming exactly what's missing:

// cannot use w (variable of type Writer) as type io.Writer value in argument to save:
//     Writer does not implement io.Writer (missing method Write)

Why Go makes you write the conversion

Most languages with numeric subtyping will quietly widen an int to a float64, or truncate an int64 into an int32, wherever the context demands it. Go's designers considered that a source of exactly the kind of bug that's expensive to find later: a silent truncation that only shows up when a value happens to exceed 32 bits in production. Go's answer is to require every conversion to be visible in the source as T(x) — nothing converts by accident, and a reviewer can see precisely where a narrowing or reinterpretation happens.

This is not the same rule as Rust's type mismatch (E0308), though the symptom looks similar: Rust infers types and then enforces exact matches after inference; Go has no inference for this case at all — assignment between differently-typed variables is categorically disallowed unless you convert.

Fix 1: numeric type mismatch

Go's numeric types — int, int8/16/32/64, uint variants, float32/64 — are all distinct types with no automatic promotion between them, even when one clearly fits inside the other. Convert explicitly:

var x int = 10
// ❌ var y int64 = x
var y int64 = int64(x)   // ✅ widening — always safe

var big int64 = 1 << 40
// ❌ var small int32 = big
var small int32 = int32(big)   // ✅ compiles, but TRUNCATES — 1<<40 doesn't fit in int32

var price float64 = 19.99
// ❌ var count int = price
var count int = int(price)   // ✅ compiles, but TRUNCATES toward zero: 19, not 20

Note the two comments above marked "compiles, but" — the compiler only enforces that the conversion is written, not that it's safe. Narrowing (int64int32) can silently drop high bits, and float→int truncates toward zero rather than rounding. If the value might not fit, check the range yourself or use math.Round before converting.

Fix 2: string vs []byte

Strings and byte slices are different types in Go even though a string is backed by bytes. Functions that expect one won't accept the other without conversion:

func writeBytes(b []byte) { /* ... */ }

s := "hello"
// ❌ writeBytes(s)
writeBytes([]byte(s))   // ✅ allocates a new byte slice, copies s

var b []byte = []byte{'h', 'i'}
// ❌ var s2 string = b
var s2 string = string(b)   // ✅ allocates a new string, copies b

Both directions allocate and copy — there's no free reinterpretation, because a string is immutable and a []byte isn't, so sharing the underlying array would violate that guarantee. If you're converting in a hot loop, that copy is worth knowing about; strings.Builder or bytes.Buffer avoid it for incremental building.

Fix 3: two named types with the same underlying type

This is the case that surprises people coming from languages with structural typing. Two types declared with type are different types even if their underlying representation is identical — Go's identity rule is by declared name, not by shape:

type Celsius float64
type Fahrenheit float64

func toF(c Celsius) Fahrenheit {
    return Fahrenheit(c*9/5 + 32)   // ✅ explicit conversion required
}

func main() {
    var c Celsius = 100
    // ❌ var f Fahrenheit = c   — cannot use c (variable of type Celsius) as Fahrenheit value in assignment
    var f Fahrenheit = Fahrenheit(c)   // ✅
    _ = f
}

This is intentional, not a limitation: it stops you from accidentally treating a temperature in Celsius as one in Fahrenheit just because both happen to be stored as float64. One exception worth knowing: an untyped constant (a literal like 98.6, not a variable) converts implicitly to any compatible named type, so toF(98.6) compiles without a cast even though toF(someFloat64Variable) would not.

Fix 4: the tail says "does not implement" — an interface, not a conversion

When the target type is an interface, the fix is different from the three above — you can't cast your way to satisfying an interface. The message tells you exactly what's missing:

type Writer interface {
    Write(p []byte) (n int, err error)
}

type logger struct{}

func (l *logger) Write(p []byte) (int, error) { return len(p), nil }

func save(w Writer) { /* ... */ }

func main() {
    l := logger{}
    // ❌ save(l)
    // cannot use l (variable of type logger) as Writer value in argument to save:
    //     logger does not implement Writer (method Write has pointer receiver)
    save(&l)   // ✅ &l is *logger, which has Write in its method set
}

Two things to check when you see this tail. First, is the method genuinely missing, or is it a typo in the name or signature (wrong parameter types, wrong return count)? Second — the case above — is the method defined on a pointer receiver (func (l *logger) Write(...))? A value type's method set only includes value-receiver methods; you must pass a pointer (&l) to satisfy an interface that requires a pointer-receiver method. This is the single most common trigger for this variant of the error.

To catch a missing-implementation bug at compile time even before you call anything, a common idiom is a standalone assertion line: var _ Writer = (*logger)(nil) — it compiles to nothing at runtime but forces the compiler to check the interface is satisfied right where you declare the type.

Quick reference

SituationFixSafe?
Widening numeric (int32int64)int64(x)Always safe
Narrowing numeric (int64int32)int32(x)Can silently truncate — check range
float→intint(x)Truncates toward zero, not rounds
string[]byte[]byte(s) / string(b)Safe, but allocates + copies
Two named types, same underlying typeOtherType(x)Safe (no data change, just re-tagged)
Message ends "does not implement"Add the missing method, or pass &xNot a conversion at all

Debugging checklist

Frequently Asked Questions

Why doesn't Go convert types automatically?

It's a deliberate design choice. Implicit numeric conversion is a well-known source of silent bugs in C-family languages — an int truncated into an int8, or an int compared against a float64 with a rounding surprise. Go requires every conversion to be written out, so the loss or reinterpretation is visible in the source rather than hidden in the compiler's judgment call.

How do I fix 'cannot use x (variable of type T) as type U'?

Convert explicitly with U(x) for numeric types, string/[]byte, and named types sharing an underlying type — for example int64(x) or MyType(x). If the tail of the message says "does not implement", the fix is different: implement the missing method on the type, or pass a pointer receiver if the method set requires one.

Why can't I assign a Celsius value to a Fahrenheit variable if both are float64?

Go's type identity rule cares about the declared name, not just the underlying representation. type Celsius float64 and type Fahrenheit float64 share an underlying type but are different named types, so assigning one to the other requires an explicit conversion: Fahrenheit(c). This is intentional — it stops you from mixing values that happen to share a representation but mean different things.

What does 'does not implement' mean at the end of the error?

It means you tried to use a concrete type where an interface was expected, and that type is missing one of the interface's methods. The message names the missing method directly. Check for two common near-misses first: a typo'd method signature, and a method defined on the pointer receiver (*T) when you're passing a value (T) — only *T satisfies the interface in that case.

Does this error also happen on return statements, not just assignment?

Yes. The same rule applies anywhere a value flows into a typed slot — assignment, a function argument, and a return statement all produce this error family, just with slightly different trailing wording ("in assignment", "in argument to f", "in return statement"). The cause and the fix (an explicit conversion) are identical in all three.

More Go & backend errors

Browse the full reference for Go, Rust, and Java errors — exact message, cause, and fix.

All Error References Go: panic: interface conversion Go: declared and not used
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.