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
undefinedvalues 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 uninitializedError 2: Modifying Constants
zig
// ❌ Error: Cannot modify constants
// const x = 10;
// x = 20; // Compile error
// ✅ Correct: Use variables
var x = 10;
x = 20; // OKError 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 integer3. 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
constandvar - ✅ 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.