A struct field can have a default value.
A default field value is used when you create a struct value and do not provide that field yourself.
const Config = struct {
port: u16 = 8080,
debug: bool = false,
};This defines a struct type named Config.
It has two fields:
port: u16 = 8080
debug: bool = falseThe field port has the default value 8080.
The field debug has the default value false.
You can create a Config value like this:
const config = Config{};This uses both default values.
So the value is the same as:
const config = Config{
.port = 8080,
.debug = false,
};Default field values let you define the normal case once, then override only the fields that need to change.
Creating a Value with Defaults
Here is a complete program:
const std = @import("std");
const Config = struct {
port: u16 = 8080,
debug: bool = false,
};
pub fn main() void {
const config = Config{};
std.debug.print("port = {}, debug = {}\n", .{
config.port,
config.debug,
});
}Output:
port = 8080, debug = falseThe expression Config{} creates a value where all fields use their defaults.
This only works because every field in Config has a default value.
Overriding One Field
You can override one field and keep the others unchanged.
const config = Config{
.debug = true,
};Now debug is true, but port still uses its default value.
The full value is:
const config = Config{
.port = 8080,
.debug = true,
};This is useful for configuration data.
const ServerConfig = struct {
host: []const u8 = "127.0.0.1",
port: u16 = 8080,
max_connections: usize = 1024,
debug: bool = false,
};
const dev = ServerConfig{
.debug = true,
};
const prod = ServerConfig{
.host = "0.0.0.0",
.port = 80,
};The dev config changes only debug.
The prod config changes only host and port.
Everything else uses the default.
Fields Without Defaults
A field does not need to have a default.
const User = struct {
id: u64,
name: []const u8,
active: bool = true,
};Here, id and name have no default values.
The field active has a default value.
So this is valid:
const user = User{
.id = 1,
.name = "Ada",
};The missing active field becomes true.
This is the same as:
const user = User{
.id = 1,
.name = "Ada",
.active = true,
};But this is not valid:
const user = User{};Zig cannot invent values for id and name.
You must provide every field that has no default.
Defaults Are Part of the Type Definition
Default values are written inside the struct definition:
const Options = struct {
verbose: bool = false,
retries: u8 = 3,
};That means every place that uses Options sees the same defaults.
This gives the type a single definition of its normal state.
Without defaults, code can become repetitive:
const a = Options{
.verbose = false,
.retries = 3,
};
const b = Options{
.verbose = false,
.retries = 3,
};With defaults, the same code becomes cleaner:
const a = Options{};
const b = Options{};The defaults live in one place.
Default Values Can Use Constants
A default value can refer to constants that are visible at that point.
const default_port: u16 = 8080;
const Config = struct {
port: u16 = default_port,
debug: bool = false,
};You can also put constants inside the struct:
const Config = struct {
const default_port: u16 = 8080;
port: u16 = default_port,
debug: bool = false,
};Now default_port belongs to the Config namespace.
You can refer to it as:
Config.default_portThis is useful when the default itself is meaningful and should have a name.
Default Values and init
Default field values do not replace init functions.
They solve different problems.
Use default field values when a field has a simple normal value:
const LoggerConfig = struct {
level: []const u8 = "info",
color: bool = true,
};Use an init function when creation needs logic:
const Buffer = struct {
data: []u8,
len: usize = 0,
pub fn init(data: []u8) Buffer {
return Buffer{
.data = data,
};
}
};Here, data must be provided by the caller. But len has a natural default of 0.
So the init function only asks for the required field:
var storage: [1024]u8 = undefined;
const buffer = Buffer.init(storage[0..]);The returned value uses:
.len = 0from the default field value.
Default Values and Mutable Structs
Default values only affect creation.
After the value exists, fields behave like normal fields.
const Counter = struct {
value: i32 = 0,
};
pub fn main() void {
var c = Counter{};
c.value += 1;
c.value += 1;
}The default gives value the initial value 0.
Then the program changes it to 2.
A default value is not a permanent rule. It is only an initial value.
Defaults Do Not Mean Optional
A default value is not the same as an optional field.
This field always exists:
debug: bool = falseEvery Config value has a debug field. If you do not provide it, Zig fills in false.
An optional field uses ?:
log_path: ?[]const u8 = nullThis means the field may contain a path, or it may contain null.
Example:
const Config = struct {
debug: bool = false,
log_path: ?[]const u8 = null,
};Now debug always has a boolean value.
log_path may or may not have a string.
const a = Config{};
const b = Config{
.log_path = "/tmp/app.log",
};In a, log_path is null.
In b, log_path contains "/tmp/app.log".
A Practical Example
Suppose we are writing options for a command-line tool.
const Options = struct {
input_path: []const u8,
output_path: []const u8 = "out.txt",
verbose: bool = false,
threads: usize = 1,
};The input path is required.
The output path, verbose flag, and thread count have defaults.
We can create a simple value:
const options = Options{
.input_path = "data.txt",
};This means:
input_path = "data.txt"
output_path = "out.txt"
verbose = false
threads = 1We can also override some fields:
const options = Options{
.input_path = "data.txt",
.output_path = "result.txt",
.threads = 4,
};Now only verbose uses its default.
This gives a clean shape to configuration data. Required fields stay visible. Optional settings stay out of the way until needed.
Common Mistake: Forgetting Required Fields
This struct has one required field:
const User = struct {
name: []const u8,
active: bool = true,
};This works:
const user = User{
.name = "Ada",
};This does not:
const user = User{};The field name has no default value, so Zig requires it.
The rule is simple: every field must get a value, either from the literal or from a default.
Common Mistake: Thinking Defaults Are Shared State
A default value does not create shared mutable state between struct values.
Each struct value gets its own field value.
const Counter = struct {
value: i32 = 0,
};
var a = Counter{};
var b = Counter{};
a.value = 10;After this:
a.value is 10
b.value is 0The default was used to initialize both values. After that, they are separate values.
The Main Idea
Default field values make struct creation easier.
They let the type define its normal state:
const Config = struct {
port: u16 = 8080,
debug: bool = false,
};Then code can create ordinary values with less repetition:
const config = Config{};Use defaults for simple, predictable initial values. Keep required fields without defaults so the caller must provide them explicitly.