Zig 函数教程
1. 函数基本语法
Section titled “1. 函数基本语法”1.1 函数声明
Section titled “1.1 函数声明”// 基本函数声明fn add(a: i8, b: i8) i8 { return a + b;}
// 带条件返回fn max(a: i32, b: i32) i32 { if (a > b) { return a; } return b;}1.2 可见性修饰符
Section titled “1.2 可见性修饰符”pub - 公开可见
Section titled “pub - 公开可见”// 其他文件可以通过 @import 导入并使用pub fn multiply(x: i32, y: i32) i32 { return x * y;}export - 导出为 C ABI
Section titled “export - 导出为 C ABI”// 在生成的目标文件中外部可见,使用 C ABIexport fn subtract(a: i32, b: i32) i32 { return a - b;}// 其他语言(如C)可以调用此函数extern - 声明外部函数
Section titled “extern - 声明外部函数”// 声明来自外部库的函数// 静态链接:链接时解析// 动态链接:运行时解析
// 来自 libc 的函数extern "c" fn printf(format: [*:0]const u8, ...) c_int;
// Windows API 函数extern "kernel32" fn ExitProcess(exit_code: u32) callconv(.winapi) noreturn;
// 指定调用约定extern "c" fn atan2(a: f64, b: f64) f64;2. 特殊函数类型
Section titled “2. 特殊函数类型”2.1 noreturn 函数
Section titled “2.1 noreturn 函数”永不返回的函数:
fn abort() noreturn { while (true) {} // 不需要 return 语句}
fn panic(message: []const u8) noreturn { std.debug.print("Panic: {s}\n", .{message}); while (true) {}}2.2 带分支提示的函数
Section titled “2.2 带分支提示的函数”fn logError(message: []const u8) void { @branchHint(.cold); // 提示编译器此函数很少被调用 std.debug.print("ERROR: {s}\n", .{message});}3. 调用约定
Section titled “3. 调用约定”3.1 .naked 调用约定
Section titled “3.1 .naked 调用约定”// 无函数序言和结语,直接汇编代码fn _start() callconv(.naked) noreturn { // 通常用于引导代码或汇编集成 asm volatile ("syscall" : : [number] "{rax}" (60), // exit 系统调用号 [arg1] "{rdi}" (0) // 退出码 : "rcx", "r11", "memory" ); unreachable;}3.2 .inline 调用约定
Section titled “3.2 .inline 调用约定”// 强制在所有调用点内联,不能内联则编译错误inline fn double(x: u32) u32 { return x * 2;}
// 实际使用:所有调用点都会展开此函数const result = double(5); // 编译为:const result = 5 * 2;使用场景:
- 调试时控制调用栈深度
- 强制编译期参数传递到返回值
- 性能测试明确需要内联
注意: 过度使用可能损害性能、增加二进制大小、减慢编译速度。
4. 参数传递
Section titled “4. 参数传递”4.1 基本类型:传值
Section titled “4.1 基本类型:传值”fn increment(x: i32) i32 { // x 是原始值的副本 return x + 1;}
test "pass by value" { const original = 5; const result = increment(original); // original 仍然是 5 // result 是 6}4.2 复合类型:传值或传引用(编译器决定)
Section titled “4.2 复合类型:传值或传引用(编译器决定)”const Point = struct { x: i32, y: i32,};
fn sumCoordinates(p: Point) i32 { // p 可能是副本,也可能是引用 // 参数不可变,编译器可以优化 return p.x + p.y;}
test "pass struct" { const point = Point{ .x = 10, .y = 20 }; const result = sumCoordinates(point); try expect(result == 30);}重要警告: 不要获取参数地址,函数返回后地址可能失效。
4.3 外部函数遵循 C ABI
Section titled “4.3 外部函数遵循 C ABI”// 对于 extern 函数,Zig 遵循 C ABI 传递结构体和联合体extern "c" fn process_point(p: Point) i32;
// 如果 Point 在 C 中按值传递,Zig 也会按值传递5. 函数参数类型推断
Section titled “5. 函数参数类型推断”5.1 anytype 参数
Section titled “5.1 anytype 参数”fn addFortyTwo(x: anytype) @TypeOf(x) { // x 的类型在调用时推断 // 返回类型与 x 相同 return x + 42;}
test "type inference" { // 调用时推断类型 const a = addFortyTwo(1); // a: comptime_int const b = addFortyTwo(@as(i64, 2)); // b: i64 const c = addFortyTwo(@as(f32, 3.0)); // c: f32
try expect(a == 43); try expect(b == 44); try expect(c == 45.0);}5.2 使用类型信息
Section titled “5.2 使用类型信息”fn genericToString(value: anytype) []const u8 { const T = @TypeOf(value);
return switch (@typeInfo(T)) { .Int => std.fmt.allocPrint(std.heap.page_allocator, "{}", .{value}), .Float => std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{value}), .Bool => if (value) "true" else "false", else => @compileError("Unsupported type"), };}6. 内联函数(深入理解)
Section titled “6. 内联函数(深入理解)”6.1 编译期参数传播
Section titled “6.1 编译期参数传播”inline fn calculate(comptime a: i32, b: i32) i32 { // a 必须是编译期已知 std.debug.print("a = {} (compile-time), b = {} (runtime)\n", .{a, b}); return a + b;}
pub fn main() void { // 由于 inline,1200 作为编译期参数传播 const result = calculate(1200, 34); // 编译时部分计算
if (result != 1234) { @compileError("bad"); // 编译期检查 }}6.2 内联与编译期执行
Section titled “6.2 内联与编译期执行”const Mode = enum { Debug, Release };
inline fn createBuffer(comptime mode: Mode) []u8 { const size = switch (mode) { .Debug => 1024, // 调试模式下更大缓冲区 .Release => 256, // 发布模式下更小 }; return std.heap.page_allocator.alloc(u8, size) catch unreachable;}
// 使用const debug_buffer = createBuffer(.Debug); // 1024 字节const release_buffer = createBuffer(.Release); // 256 字节7. 函数指针
Section titled “7. 函数指针”7.1 基本用法
Section titled “7.1 基本用法”// 函数指针类型const MathOp = *const fn (a: i32, b: i32) i32;
fn add(a: i32, b: i32) i32 { return a + b; }fn multiply(a: i32, b: i32) i32 { return a * b; }
fn doOperation(op: MathOp, x: i32, y: i32) i32 { return op(x, y);}
test "function pointers" { try expect(doOperation(add, 3, 4) == 7); try expect(doOperation(multiply, 3, 4) == 12);}7.2 函数指针与闭包
Section titled “7.2 函数指针与闭包”// Zig 没有闭包,但可以使用结构体模拟const Adder = struct { increment: i32,
fn call(self: Adder, x: i32) i32 { return x + self.increment; }};
const AdderFn = *const fn (context: *Adder, x: i32) i32;
fn makeAdder(increment: i32) Adder { return Adder{ .increment = increment };}8. 函数反射
Section titled “8. 函数反射”8.1 获取函数信息
Section titled “8.1 获取函数信息”const std = @import("std");const testing = std.testing;
test "function reflection" { // 获取函数类型信息 const ExpectFn = @TypeOf(testing.expect); const fn_info = @typeInfo(ExpectFn).@"fn";
// 检查参数 try expect(fn_info.params.len >= 1); try expect(fn_info.params[0].type.? == bool);
// 检查返回类型 try expect(fn_info.return_type.? == void);
// 检查是否是泛型函数 const Log2IntFn = @TypeOf(std.math.Log2Int); try expect(@typeInfo(Log2IntFn).@"fn".is_generic == true);}8.2 反射实用示例
Section titled “8.2 反射实用示例”fn analyzeFunction(comptime Func: type) void { const info = @typeInfo(Func);
switch (info) { .@"fn" => |fn_info| { std.debug.print("Function info:\n", .{}); std.debug.print(" Return type: {}\n", .{fn_info.return_type}); std.debug.print(" Is generic: {}\n", .{fn_info.is_generic}); std.debug.print(" Calling convention: {}\n", .{fn_info.calling_convention});
for (fn_info.params, 0..) |param, i| { std.debug.print(" Param {}: {?s} : {?}\n", .{ i, param.name, param.type }); } }, else => @compileError("Not a function type"), }}
// 使用analyzeFunction(@TypeOf(std.mem.copy));9. 实用模式
Section titled “9. 实用模式”9.1 高阶函数
Section titled “9.1 高阶函数”fn map( comptime T: type, data: []const T, comptime mapper: fn (T) T) []T { const result = std.heap.page_allocator.alloc(T, data.len); for (data, 0..) |item, i| { result[i] = mapper(item); } return result;}
fn square(x: i32) i32 { return x * x; }fn double(x: i32) i32 { return x * 2; }
test "higher-order function" { const data = [_]i32{1, 2, 3, 4};
const squared = map(i32, &data, square); const doubled = map(i32, &data, double);
// 清理内存...}9.2 回调模式
Section titled “9.2 回调模式”const EventHandler = *const fn (context: *anyopaque, data: *const EventData) void;
const EventSystem = struct { handlers: std.ArrayList(EventHandler), contexts: std.ArrayList(*anyopaque),
fn register(self: *EventSystem, handler: EventHandler, context: *anyopaque) void { self.handlers.append(handler); self.contexts.append(context); }
fn trigger(self: *EventSystem, data: *const EventData) void { for (self.handlers.items, self.contexts.items) |handler, context| { handler(context, data); } }};10. 最佳实践总结
Section titled “10. 最佳实践总结”- 默认使用普通函数,让编译器决定是否内联
- 谨慎使用
inline,只在明确需要时 - 优先使用具体类型,类型推断保留给泛型代码
- 合理使用函数指针,但注意 Zig 没有闭包
- 遵循 C ABI 进行跨语言交互
- 利用反射进行元编程,但要保持可读性
- 注意参数传递语义,避免不必要的复制
11. 完整示例
Section titled “11. 完整示例”const std = @import("std");
// 公开 API 函数pub fn create_vector(x: f32, y: f32, z: f32) Vector { return Vector{ .x = x, .y = y, .z = z };}
// 内部辅助函数fn dot_product(a: Vector, b: Vector) f32 { return a.x * b.x + a.y * b.y + a.z * b.z;}
// 泛型数学函数fn lerp(comptime T: type, a: T, b: T, t: f32) T { return a + (b - a) * t;}
// C 导出接口export fn compute_length(vec: *const Vector) f32 { return std.math.sqrt(dot_product(vec.*, vec.*));}
// 回调系统pub const Callback = struct { func: *const fn (data: []const u8) void, user_data: *anyopaque,};
pub fn register_callback(cb: Callback) void { // 注册逻辑...}
const Vector = struct { x: f32, y: f32, z: f32,};
test "function features" { const v1 = create_vector(1.0, 2.0, 3.0); const v2 = create_vector(4.0, 5.0, 6.0);
const dot = dot_product(v1, v2); try expect(dot == 32.0); // 1*4 + 2*5 + 3*6 = 32
const result = lerp(f32, 10.0, 20.0, 0.5); try expect(result == 15.0);}通过掌握 Zig 函数的各种特性,你可以编写出高效、类型安全且易于维护的代码。