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 errorYou must specify the conversion explicitly:
var result float64 = float64(number)This makes the following potential problems visible in the code:
- loss of precision;
- overflow;
- loss of part of the value;
- differences between types and platforms.
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 = valueAn 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 → intThe general syntax is:
T(x)Where:
Tis the target type;xis the original value.
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)) // -3The 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) // 705032704Go 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) // 9007199254740992The 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 errorThe values must first be converted to the same type:
result := int64(a) + bConversions 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) // 705032704For 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) // 42Large integers may lose precision.
Floating-Point to Integer
number := 3.99
result := int(number)
fmt.Println(result) // 3The 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)) // 4Convert 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) // 1000000000000The same rule is important for division:
total := 100
count := 3
average := total / count
fmt.Println(average) // 33Both 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) // AThe 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) // 65The reverse conversion:
number, err := strconv.Atoi("65")
if err != nil {
fmt.Println("invalid number")
return
}
fmt.Println(number) // 65For 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("Привет"))) // 6The 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)) // HelloUser-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 errorAn 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 212A real temperature conversion requires a formula:
f := Fahrenheit(c*9/5 + 32)
fmt.Println(f) // 212Pointer Conversions
Regular pointers of different types cannot be converted directly:
var number int = 10
var pointer *float64 = (*float64)(&number) // compilation errorSuch 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 = valueThis 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 uint8A variable already has a specific type:
var value = 100 // int
var result int64 = value // compilation errorAn 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 errorThey must first be converted to the same type:
fmt.Println(int64(a) == b) // trueComparison with an untyped constant is allowed:
fmt.Println(a == 5) // true
fmt.Println(b == 5) // trueThe 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) // 0Integer 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 errorYou must explicitly get the number’s string representation:
text := "Number: " + strconv.Itoa(number)Or use formatting:
text := fmt.Sprintf("Number: %d", number)Summary
- Type conversion uses the syntax
TargetType(value). - Variables of different numeric types require explicit conversion.
- Converting
float64tointdiscards the fractional part. - Converting to a smaller integer type may discard higher bits.
- Converting a large integer to
float64may lose precision. - Convert operands before an arithmetic operation if the calculation must use the target type.
string(65)returns"A", not"65".- Use the
strconvpackage to convert a number to text. - Untyped constants can receive an appropriate type from their context.
- A type assertion checks the concrete type of an interface value and is not a type conversion.
The main reason Go does not support implicit numeric conversions is that potential data loss should be clearly visible in the code.