Skip to content

Zig Variable Declaration

In Zig, variable declaration is the foundation of programming. This chapter will详细介绍 how to declare and use variables in Zig.

Variable Declaration Basics

const Declaration (Constants)

const is used to declare immutable values:

zig
const std = @import("std");

pub fn main() void {
    const name = "张三";
    const age = 25;
    const pi = 3.14159;

    std.debug.print("Name: {s}, Age: {}, π ≈ {d:.2}\n", .{ name, age, pi });
}

Features:

  • Value cannot be modified once determined at compile-time or runtime
  • Must be initialized at declaration
  • Can be computed at compile-time

var Declaration (Variables)

var is used to declare mutable values:

zig
const std = @import("std");

pub fn main() void {
    var counter = 0;
    var message = "Start counting";

    std.debug.print("{s}: {}\n", .{ message, counter });

    counter = 10;
    message = "Count updated";

    std.debug.print("{s}: {}\n", .{ message, counter });
}

Features:

  • Value can be modified at runtime
  • Must be initialized at declaration
  • Once type is determined, it cannot change

Type Inference and Explicit Types

Type Inference

Zig can automatically infer variable types:

zig
const std = @import("std");

pub fn main() void {
    // Type inference
    const number = 42;        // inferred as comptime_int
    const float_num = 3.14;   // inferred as comptime_float
    const text = "Hello";     // inferred as *const [5:0]u8
    const flag = true;        // inferred as bool

    std.debug.print("Number: {}, Float: {d:.2}, Text: {s}, Bool: {}\n",
                    .{ number, float_num, text, flag });
}

Explicit Type Declaration

You can also explicitly specify types:

zig
const std = @import("std");

pub fn main() void {
    // Explicit type declaration
    const small_number: i8 = 42;
    const big_number: i64 = 1000000;
    const precise_float: f64 = 3.141592653589793;
    const single_char: u8 = 'A';

    std.debug.print("Small integer: {}, Big integer: {}, Precise float: {d:.10}, Char: {c}\n",
                    .{ small_number, big_number, precise_float, single_char });
}

Variable Scopes

Block Scopes

Variable scope is determined by code blocks:

zig
const std = @import("std");

pub fn main() void {
    const global_var = "Global variable";

    {
        const local_var = "Local variable";
        std.debug.print("Inner block: {s}, {s}\n", .{ global_var, local_var });
    }

    // local_var is not accessible here
    std.debug.print("Outer block: {s}\n", .{global_var});
}

Variable Shadowing

Inner scopes can declare variables with the same name, shadowing outer variables:

zig
const std = @import("std");

pub fn main() void {
    const value = 10;
    std.debug.print("Outer value: {}\n", .{value});

    {
        const value = 20; // shadows outer value
        std.debug.print("Inner value: {}\n", .{value});
    }

    std.debug.print("Outer value: {}\n", .{value}); // still 10
}

Undefined Values

undefined

undefined represents uninitialized values:

zig
const std = @import("std");

pub fn main() void {
    var x: i32 = undefined; // declare but don't initialize
    x = 42; // assign value later

    std.debug.print("x value: {}\n", .{x});

    // Note: Using undefined values is undefined behavior
    // var y: i32 = undefined;
    // std.debug.print("{}\n", .{y}); // Dangerous!
}

Important Notes:

  • Using undefined values leads to undefined behavior
  • Primarily used for performance optimization, skipping initialization
  • Must assign value before using

Compile-time Variables

comptime Variables

comptime variables are calculated at compile time:

zig
const std = @import("std");

pub fn main() void {
    comptime var compile_time_counter = 0;

    // Compile-time loop
    comptime {
        var i = 0;
        while (i < 5) : (i += 1) {
            compile_time_counter += 1;
        }
    }

    std.debug.print("Compile-time calculation result: {}\n", .{compile_time_counter});
}

Compile-time Constants

zig
const std = @import("std");

// Compile-time constants
const BUFFER_SIZE = 1024;
const VERSION = "1.0.0";
const DEBUG_MODE = true;

pub fn main() void {
    std.debug.print("Buffer size: {}\n", .{BUFFER_SIZE});
    std.debug.print("Version: {s}\n", .{VERSION});
    std.debug.print("Debug mode: {}\n", .{DEBUG_MODE});
}

Arrays and Slice Variables

Array Variables

zig
const std = @import("std");

pub fn main() void {
    // Fixed-size arrays
    var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    const fruits = [_][]const u8{ "苹果", "香蕉", "橙子" };

    // Modify array elements
    numbers[0] = 10;

    std.debug.print("Numbers: ");
    for (numbers) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");

    std.debug.print("Fruits: ");
    for (fruits) |fruit| {
        std.debug.print("{s} ", .{fruit});
    }
    std.debug.print("\n");
}

Slice Variables

zig
const std = @import("std");

pub fn main() void {
    var array = [_]i32{ 1, 2, 3, 4, 5 };

    // Slice is a reference to a part of the array
    var slice: []i32 = array[1..4]; // contains indices 1, 2, 3
    const const_slice: []const i32 = array[0..];

    // Modifying slice affects original array
    slice[0] = 100;

    std.debug.print("Original array: ");
    for (array) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");

    std.debug.print("Slice: ");
    for (slice) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");
}

Struct Variables

zig
const std = @import("std");

const Person = struct {
    name: []const u8,
    age: u32,
    height: f32,
};

pub fn main() void {
    // Struct variable
    var person = Person{
        .name = "李四",
        .age = 30,
        .height = 175.5,
    };

    std.debug.print("Name: {s}, Age: {}, Height: {d:.1}cm\n",
                    .{ person.name, person.age, person.height });

    // Modify struct fields
    person.age = 31;
    person.height = 176.0;

    std.debug.print("Updated - Name: {s}, Age: {}, Height: {d:.1}cm\n",
                    .{ person.name, person.age, person.height });
}

Optional Type Variables

zig
const std = @import("std");

pub fn main() void {
    // Optional type variables
    var maybe_number: ?i32 = null;
    var maybe_name: ?[]const u8 = "王五";

    std.debug.print("maybe_number: ");
    if (maybe_number) |num| {
        std.debug.print("{}\n", .{num});
    } else {
        std.debug.print("null\n", .{});
    }

    std.debug.print("maybe_name: ");
    if (maybe_name) |name| {
        std.debug.print("{s}\n", .{name});
    } else {
        std.debug.print("null\n", .{});
    }

    // Assign values
    maybe_number = 42;
    maybe_name = null;

    std.debug.print("After assignment:\n");
    std.debug.print("maybe_number: {?}\n", .{maybe_number});
    std.debug.print("maybe_name: {?s}\n", .{maybe_name});
}

Variable Initialization Patterns

Default Initialization

zig
const std = @import("std");

pub fn main() void {
    // Default values for basic types
    var int_var: i32 = 0;
    var float_var: f64 = 0.0;
    var bool_var: bool = false;
    var optional_var: ?i32 = null;

    std.debug.print("Integer: {}, Float: {d:.1}, Bool: {}, Optional: {?}\n",
                    .{ int_var, float_var, bool_var, optional_var });
}

Batch Initialization

zig
const std = @import("std");

pub fn main() void {
    // Batch initialization of arrays
    var zeros = [_]i32{0} ** 5; // 5 zeros
    var ones = [_]i32{1} ** 3;  // 3 ones

    std.debug.print("zeros: ");
    for (zeros) |val| {
        std.debug.print("{} ", .{val});
    }
    std.debug.print("\n");

    std.debug.print("ones: ");
    for (ones) |val| {
        std.debug.print("{} ", .{val});
    }
    std.debug.print("\n");
}

Common Errors and Notes

Error 1: Uninitialized Variables

zig
// ❌ Error: Variables must be initialized
// var x: i32; // Compile error

// ✅ Correct: Explicit initialization
var x: i32 = 0;
// or
var y: i32 = undefined; // Explicitly uninitialized

Error 2: Modifying Constants

zig
// ❌ Error: Cannot modify constants
// const x = 10;
// x = 20; // Compile error

// ✅ Correct: Use variables
var x = 10;
x = 20; // OK

Error 3: Type Mismatch

zig
// ❌ Error: Type mismatch
// var x: i32 = 3.14; // Compile error

// ✅ Correct: Type matching
var x: f32 = 3.14;
// or type conversion
var y: i32 = @intFromFloat(3.14);

Best Practices

1. Prefer const

zig
// ✅ Good practice: Use const by default
const name = "username";
const config = loadConfig();

// Use var only when modification is needed
var counter = 0;
var current_state = State.Initial;

2. Explicit Type Declarations

zig
// ✅ When type is not obvious, declare explicitly
const buffer: [1024]u8 = undefined;
const timeout: u64 = 5000; // milliseconds

// When type is obvious, can be omitted
const message = "Hello"; // Obviously a string
const count = 42;        // Obviously an integer

3. Meaningful Variable Names

zig
// ✅ Good variable names
const user_count = 100;
const max_retry_attempts = 3;
const is_debug_enabled = true;

// ❌ Avoid variable names
const x = 100;
const n = 3;
const flag = true;

4. Appropriate Scopes

zig
pub fn processData() void {
    const input_file = "data.txt";

    // Limit variable scope
    {
        const temp_buffer = allocateBuffer();
        defer freeBuffer(temp_buffer);
        // Use temp_buffer
    }

    // temp_buffer is not accessible here
}

Practice Exercises

Exercise 1: Basic Variable Operations

zig
const std = @import("std");

pub fn main() void {
    // TODO: Declare an integer variable with initial value 10
    // TODO: Declare a string constant with your name
    // TODO: Declare a float variable with initial value 0.0
    // TODO: Modify integer variable to 20
    // TODO: Modify float variable to 3.14
    // TODO: Print all variable values
}

Exercise 2: Arrays and Structs

zig
const std = @import("std");

const Student = struct {
    name: []const u8,
    age: u8,
    grades: [3]f32,
};

pub fn main() void {
    // TODO: Create a student variable
    // TODO: Modify student's age
    // TODO: Modify first grade
    // TODO: Calculate average grade
    // TODO: Print student information and average grade
}

Summary

This chapter introduced all aspects of variable declaration in Zig:

  • ✅ Difference between const and var
  • ✅ Type inference and explicit type declaration
  • ✅ Variable scopes and shadowing
  • ✅ Use of special value undefined
  • ✅ Compile-time variables and constants
  • ✅ Complex type variable declarations
  • ✅ Common errors and best practices

Mastering variable declaration is the foundation of Zig programming. In the next chapter, we will learn about Zig's data type system.

Content is for learning and research only.