@intCast
@intCast converts one integer value to another integer type.
You use it when Zig needs an explicit promise that the integer value fits in the destination type.
const small: u8 = @intCast(big);This says:
Convert big to u8.But the value must fit in u8.
A u8 can store values from 0 to 255.
Why @intCast Exists
Integer conversions can lose information.
Example:
const big: u16 = 300;
const small: u8 = big;This is not allowed.
Why? Because 300 cannot fit in u8.
If Zig silently allowed this, the program could produce a wrong value.
So Zig requires an explicit cast:
const big: u16 = 200;
const small: u8 = @intCast(big);This is allowed because 200 fits in u8.
The Destination Type Comes from Context
In modern Zig, @intCast does not take the destination type as an argument.
You write:
const small: u8 = @intCast(big);The destination type is u8, because the variable says:
const small: u8You can also provide the type with @as:
const small = @as(u8, @intCast(big));This means the same thing, but it is more explicit.
Compile-Time Checking
If the compiler knows the value does not fit, it rejects the code.
const x: u16 = 300;
const y: u8 = @intCast(x);Since x is known at compile time, Zig can see that 300 does not fit in u8.
A valid example:
const x: u16 = 200;
const y: u8 = @intCast(x);This works.
Runtime Checking
Sometimes the value is known only when the program runs.
fn shrink(x: u16) u8 {
return @intCast(x);
}This function says: convert x to u8.
But not every u16 fits in u8.
In safe build modes, Zig checks the value at runtime. If the value is too large, the program traps.
A safer version returns an error:
fn shrink(x: u16) !u8 {
if (x > 255) return error.TooLarge;
return @intCast(x);
}Now the function handles the problem directly.
Signed and Unsigned Integers
@intCast also handles signedness changes.
Example:
const x: i32 = 100;
const y: u32 = @intCast(x);This works because 100 can be represented as u32.
This does not work safely:
const x: i32 = -1;
const y: u32 = @intCast(x);A negative value cannot be represented as an unsigned integer.
A safe version checks first:
fn toUnsigned(x: i32) !u32 {
if (x < 0) return error.Negative;
return @intCast(x);
}Widening Usually Does Not Need @intCast
If the destination type can represent every value of the source type, Zig can often convert without @intCast.
Example:
const small: u8 = 200;
const big: u16 = small;Every u8 value fits in u16, so this is safe.
But narrowing needs a cast:
const big: u16 = 200;
const small: u8 = @intCast(big);Narrowing means converting to a type with a smaller range.
@intCast Is Not @truncate
@intCast checks that the value fits.
@truncate keeps only the low bits and discards the rest.
Example:
const x: u16 = 300;
const y: u8 = @truncate(x);300 in hexadecimal is:
0x012cThe low 8 bits are:
0x2cSo y becomes 44.
That is not a safe numeric conversion. It is bit-level truncation.
Use @intCast when you want the same numeric value in a different integer type.
Use @truncate when you intentionally want to discard high bits.
Common Example: Indexes
Many lengths and indexes use usize.
But sometimes an API expects a smaller integer type.
fn writeByteCount(count: usize) !u8 {
if (count > 255) return error.TooLarge;
return @intCast(count);
}This is clear:
The input may be large.
The output must fit in one byte.
The function checks before casting.Common Example: File Formats
Binary file formats often use fixed-size integer fields.
For example, a format may store a length as u16.
fn encodeLength(len: usize) !u16 {
if (len > std.math.maxInt(u16)) {
return error.LengthTooLarge;
}
return @intCast(len);
}This avoids silent overflow.
The cast is correct because the function checks the range first.
Using std.math.maxInt
Instead of writing 255, use std.math.maxInt for clarity:
const std = @import("std");
fn shrink(x: u16) !u8 {
if (x > std.math.maxInt(u8)) return error.TooLarge;
return @intCast(x);
}This is better because the limit is tied to the destination type.
For signed types, you may also use std.math.minInt.
const std = @import("std");
fn toI8(x: i32) !i8 {
if (x < std.math.minInt(i8)) return error.TooSmall;
if (x > std.math.maxInt(i8)) return error.TooLarge;
return @intCast(x);
}@intCast Does Not Change Meaning
@intCast is a numeric conversion.
It tries to preserve the number.
Example:
const x: u16 = 42;
const y: u8 = @intCast(x);Both x and y mean the number 42.
This is different from @bitCast, which preserves raw bits.
For integer type changes, prefer @intCast unless you truly need bit-level behavior.
How to Read @intCast
When you see:
const y: T = @intCast(x);read it as:
Convert integer x to type T, and require that the value fits.Then ask:
Can every possible x fit in T?
If not, where is the range check?
Should this function return an error instead of trapping?That habit prevents many integer bugs.
Key Idea
@intCast converts an integer to another integer type while preserving the numeric value.
It is used when the conversion may be unsafe without a range check.
Use it for explicit integer narrowing or signedness changes. Check the range yourself when invalid input is possible.