Zig does not perform most numeric conversions automatically.
This is intentional. Conversions can lose information, change sign, truncate values, or change precision. Zig requires the programmer to write conversions explicitly.
Consider:
const a: i32 = 10;
const b: u32 = 20;
const c = a + b; // errorThe types differ. Zig does not guess which conversion is correct.
You must convert one value explicitly.
const a: i32 = 10;
const b: u32 = 20;
const c = a + @as(i32, @intCast(b));This conversion has two parts.
@intCast(b)This converts the integer value.
@as(i32, ...)This supplies the destination type.
The compiler checks whether the conversion is valid.
If the value cannot fit, the program traps in safe modes.
const big: u32 = 1000;
const small: u8 = @intCast(big); // trap in safe mode1000 does not fit in u8.
An integer may be converted to a floating-point value.
const n: i32 = 10;
const x = @as(f64, @floatFromInt(n));x is an f64 with value 10.0.
A floating-point value may be converted to an integer.
const x: f64 = 3.75;
const n: i32 = @intFromFloat(x);The fractional part is discarded.
Here n becomes 3.
The conversion must still fit in the destination type.
const x: f64 = 1000.0;
const n: u8 = @intFromFloat(x); // trap in safe modeA value may also be converted between floating-point types.
const x: f64 = 3.141592653589793;
const y: f32 = @floatCast(x);The result may lose precision because f32 has fewer bits.
Sometimes the destination type is already known from context.
const x: f32 = @floatCast(3.14);The compiler sees that the result must be f32.
When the context is not enough, use @as.
const x = @as(f32, @floatCast(3.14));Pointers also require explicit conversions.
const std = @import("std");
pub fn main() void {
var x: i32 = 10;
const p: *i32 = &x;
std.debug.print("{d}\n", .{p.*});
}A pointer conversion is not an integer conversion. Zig separates these operations clearly.
Bit-level reinterpretation uses different operations than numeric conversion.
const bits: u32 = 0x40490fdb;
const pi: f32 = @bitCast(bits);@bitCast does not change the bits. It reinterprets them as another type of the same size.
This differs from numeric conversion.
const pi: f32 = @floatFromInt(bits); // numeric conversionThe two operations mean completely different things.
Integer widening is usually safe.
const small: u8 = 100;
const big: u32 = small;The value fits naturally.
Integer narrowing requires an explicit cast.
const big: u32 = 100;
const small: u8 = @intCast(big);This prevents accidental truncation.
Explicit casts make conversions visible. When a reader sees a cast, they immediately know the code is crossing a type boundary.
Use casts carefully. Most unnecessary casts are a sign that the types should be designed more clearly.
Exercises:
Convert a
u8value tou32.Convert an
i32tof64and print the result.Convert
7.9to an integer and print the result.Attempt to cast
300intou8and observe the behavior in a safe build.Use
@bitCastto reinterpret au32value asf32.