Skip to main content

How does type conversion work in Go? Why is there no implicit conversion?

Brief Answer

Type conversion creates a value of one type based on a value of another type.

Syntax:

TargetType(value)

Example:

var number int = 10
var result float64 = float64(number)

Go does not automatically convert variables between different numeric types:

var number int = 10
var result float64 = number // compilation error

You must specify the conversion explicitly:

var result float64 = float64(number)

This makes the following potential problems visible in the code:

The main exception is untyped constants. They can automatically receive an appropriate type if their value can be represented by that type:

const value = 10

var a int = value
var b int64 = value
var c float64 = value

An explicit conversion does not guarantee that the data will be preserved. For example, converting int64 to int32 may discard higher bits, while converting float64 to int discards the fractional part.


What Is Type Conversion?

Type conversion creates a new value of the specified type.

i := 42
f := float64(i) // int → float64

x := 3.9
n := int(x) // float64 → int

The general syntax is:

T(x)

Where:

Why Does Go Not Perform Implicit Numeric Conversions?

1. A Conversion May Lose Data

When a floating-point number is converted to an integer, its fractional part is discarded:

fmt.Println(int(3.9))  // 3
fmt.Println(int(-3.9)) // -3

The value is truncated toward zero. It is not rounded.

When a large number is converted to a smaller integer type, higher bits may be discarded:

var big int64 = 5_000_000_000

small := int32(big)

fmt.Println(small) // 705032704

Go requires the explicit int32(big) conversion so that this potentially dangerous operation is visible in the code.

2. Precision May Be Lost

Not every int64 value can be represented exactly as a float64:

var number int64 = 9_007_199_254_740_993

result := float64(number)

fmt.Printf("%.0f\n", result) // 9007199254740992

The value changes because float64 cannot represent every large integer exactly.

3. The Code Becomes Easier to Understand

An explicit conversion clearly shows the developer’s intention:

result := float64(count)

It is immediately clear that the value is intentionally being converted to a floating-point type.

Without explicit conversion syntax, it would be harder to tell whether the type change was intentional or accidental.

4. The Rules Remain Simple and Predictable

Go does not have a complex system of automatic numeric type promotion.

For example, you cannot directly add an int and an int64:

var a int = 10
var b int64 = 20

result := a + b // compilation error

The values must first be converted to the same type:

result := int64(a) + b

Conversions Between Integer Types

var value int64 = 100

a := int(value)
b := int32(value)
c := uint(value)

The conversion does not check whether the original value fits into the target type:

var big int64 = 5_000_000_000

small := int32(big)

fmt.Println(small) // 705032704

For this reason, you may need to check the range manually before a potentially unsafe conversion:

if big < math.MinInt32 || big > math.MaxInt32 {
	return errors.New("value does not fit into int32")
}

small := int32(big)

Converting Between Integer and Floating-Point Types

Integer to Floating-Point

number := 42
result := float64(number)

fmt.Println(result) // 42

Large integers may lose precision.

Floating-Point to Integer

number := 3.99
result := int(number)

fmt.Println(result) // 3

The fractional part is discarded, not rounded.

To round a number, use functions from the math package:

number := 3.99

fmt.Println(math.Round(number)) // 4
fmt.Println(math.Floor(number)) // 3
fmt.Println(math.Ceil(number))  // 4

Convert Before Performing the Calculation

Converting the result does not protect the original expression from overflow.

var a int32 = 1_000_000
var b int32 = 1_000_000

result := int64(a * b)

Here, a * b is calculated as an int32 first. The overflow happens before the result is converted to int64.

The operands must be converted before the calculation:

result := int64(a) * int64(b)

fmt.Println(result) // 1000000000000

The same rule is important for division:

total := 100
count := 3

average := total / count

fmt.Println(average) // 33

Both values have the type int, so integer division is performed.

To get a floating-point result:

average := float64(total) / float64(count)

fmt.Println(average) // 33.333333...

Converting a Number to a String

The expression string(number) does not convert a number into a string containing its digits:

number := 65
text := string(number)

fmt.Println(text) // A

The value 65 is interpreted as a Unicode code point corresponding to the character A.

To get the string "65", use the strconv package:

import "strconv"

number := 65
text := strconv.Itoa(number)

fmt.Println(text) // 65

The reverse conversion:

number, err := strconv.Atoi("65")

if err != nil {
	fmt.Println("invalid number")
	return
}

fmt.Println(number) // 65

For other types:

text := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
flag := strconv.FormatBool(true)              // "true"

Converting Between string, []byte, and []rune

text := "Привет"

bytes := []byte(text)
runes := []rune(text)

textFromBytes := string(bytes)
textFromRunes := string(runes)

[]byte contains the individual UTF-8 bytes:

fmt.Println(len([]byte("Привет"))) // 12

[]rune contains Unicode code points:

fmt.Println(len([]rune("Привет"))) // 6

The resulting slice can be modified independently of the original string:

text := "hello"
bytes := []byte(text)

bytes[0] = 'H'

fmt.Println(text)          // hello
fmt.Println(string(bytes)) // Hello

User-Defined Types

Different named types are considered different types, even if they have the same underlying type:

type Celsius float64
type Fahrenheit float64

var c Celsius = 100

var f Fahrenheit = c // compilation error

An explicit conversion is required:

var f Fahrenheit = Fahrenheit(c)

However, such a conversion changes only the type, not the physical meaning of the value:

f := Fahrenheit(c)

fmt.Println(f) // 100, not 212

A real temperature conversion requires a formula:

f := Fahrenheit(c*9/5 + 32)

fmt.Println(f) // 212

Pointer Conversions

Regular pointers of different types cannot be converted directly:

var number int = 10

var pointer *float64 = (*float64)(&number) // compilation error

Such operations are possible through the unsafe package, but they bypass Go’s type-safety system:

pointer := (*float64)(unsafe.Pointer(&number))

This is rarely necessary and may cause incorrect program behavior.

Type Assertion Is Not Type Conversion

When a value is stored in an interface, a type assertion is used to retrieve its concrete type:

var value any = "hello"

text, ok := value.(string)

if ok {
	fmt.Println(text)
}

Go does not convert the value to string here. It checks whether the value stored inside the interface has the type string.

The unsafe form is:

text := value.(string)

If the interface contains a value of another type, the program will panic.

Special Case: Untyped Constants

An untyped constant can receive a type from its context:

const value = 100

var a int = value
var b int64 = value
var c float64 = value

This is possible only when the value can be represented by the target type:

const value = 300

var number uint8 = value // error: 300 does not fit into uint8

A variable already has a specific type:

var value = 100 // int

var result int64 = value // compilation error

An explicit conversion is required:

var result int64 = int64(value)

Therefore, the precise rule is:

> Go does not perform implicit conversions between variables of different numeric types, but untyped constants can receive an appropriate type from their context.

Comparing Different Types

Variables of different numeric types cannot be compared directly:

var a int = 5
var b int64 = 5

fmt.Println(a == b) // compilation error

They must first be converted to the same type:

fmt.Println(int64(a) == b) // true

Comparison with an untyped constant is allowed:

fmt.Println(a == 5) // true
fmt.Println(b == 5) // true

The constant 5 receives the required type from the context.

Practical Example: Calculating a Percentage

done := 50
total := 75

percent := done / total * 100

fmt.Println(percent) // 0

Integer division 50 / 75 is performed first, producing 0.

Correct version:

percent := float64(done) / float64(total) * 100

fmt.Println(percent) // 66.666666...

Practical Example: Combining a String and a Number

Go does not automatically combine a number with a string:

number := 42

text := "Number: " + number // compilation error

You must explicitly get the number’s string representation:

text := "Number: " + strconv.Itoa(number)

Or use formatting:

text := fmt.Sprintf("Number: %d", number)

Summary

The main reason Go does not support implicit numeric conversions is that potential data loss should be clearly visible in the code.

Serzh AI Academy — How does type conversion work in Go? Why is there no implicit conversion?