A declaration may name its type explicitly.
const n: i32 = 10;The part after the colon is the type:
i32A declaration may also leave the type out.
const n = 10;In this case, Zig infers the type from the value and the surrounding context.
Type inference removes repetition. It does not remove types. Every value still has a type.
const std = @import("std");
pub fn main() void {
const a: i32 = 10;
const b = a + 20;
std.debug.print("{d}\n", .{b});
}Here a is an i32. The value 20 is used with an i32, so b is also an i32.
Inference often works best for local names.
const width = 80;
const height = 25;
const area = width * height;The code is clear without repeating the type.
Explicit types are better when the exact representation matters.
const byte: u8 = 255;
const index: usize = 0;
const offset: i64 = -12;These declarations say more than the values alone.
A function parameter always needs a type.
fn square(n: i32) i32 {
return n * n;
}The return type is also written explicitly.
fn square(n: i32) i32Zig does not infer function parameter types from calls. The function declaration is the contract.
Sometimes a literal has no final type until context gives it one.
const a: i32 = 10;
const b: u8 = 10;
const c: f64 = 10;The literal 10 can be used in several types, provided the value fits.
This is valid:
const x: u8 = 255;This is not:
const x: u8 = 256; // errorThe value does not fit in u8.
An inferred type may be more specific than expected.
const x = @as(u8, 10);Here x is a u8, because the expression has type u8.
Arrays infer their length and element type.
const data = [_]u8{ 1, 2, 3 };The type is:
[3]u8The underscore asks the compiler to count the elements.
For a slice, write the slice type.
const data: []const u8 = "hello";This says data is a slice of constant bytes.
Without the explicit slice type, the string literal keeps its more exact array-pointer type.
const data = "hello";This is often fine, but sometimes the explicit slice type is clearer.
Inference also works with structs.
const Point = struct {
x: i32,
y: i32,
};
const p = Point{ .x = 3, .y = 4 };The value on the right is a Point, so p is a Point.
Inside a struct literal, field names make the code explicit.
Point{ .x = 3, .y = 4 }The compiler knows which type is being built from context.
const p: Point = .{ .x = 3, .y = 4 };Here the type is written on the left, so the right side can use the shorter form.
Use inference when it makes the code shorter without hiding important information.
const len = buffer.len;
const first = buffer[0];Use explicit types when they are part of the meaning.
const max_packet_size: u16 = 1500;
var pos: usize = 0;A good rule is simple: write the type when the reader needs it. Leave it out when the expression already says enough.
Exercises:
Declare
const n = 10;, then use it in an expression with an expliciti32.Declare a
u8value using an explicit type. Try a value that is too large.Create an array with
[_]u8{ ... }and check its length.Define a
Pointstruct and create a value using full syntax.Create the same
Pointvalue using type context and the shorter.{ ... }syntax.