Skip to content

Explicit Casts

Zig does not perform most numeric conversions automatically.

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; // error

The 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 mode

1000 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 mode

A 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 conversion

The 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:

  1. Convert a u8 value to u32.

  2. Convert an i32 to f64 and print the result.

  3. Convert 7.9 to an integer and print the result.

  4. Attempt to cast 300 into u8 and observe the behavior in a safe build.

  5. Use @bitCast to reinterpret a u32 value as f32.