Functions

functions.zig

  1. const expect = @import("std").testing.expect;
  2. // Functions are declared like this
  3. fn add(a: i8, b: i8) i8 {
  4. if (a == 0) {
  5. return b;
  6. }
  7. return a + b;
  8. }
  9. // The export specifier makes a function externally visible in the generated
  10. // object file, and makes it use the C ABI.
  11. export fn sub(a: i8, b: i8) i8 { return a - b; }
  12. // The extern specifier is used to declare a function that will be resolved
  13. // at link time, when linking statically, or at runtime, when linking
  14. // dynamically.
  15. // The callconv specifier changes the calling convention of the function.
  16. extern "kernel32" fn ExitProcess(exit_code: u32) callconv(if (@import("builtin").target.cpu.arch == .i386) .Stdcall else .C) noreturn;
  17. extern "c" fn atan2(a: f64, b: f64) f64;
  18. // The @setCold builtin tells the optimizer that a function is rarely called.
  19. fn abort() noreturn {
  20. @setCold(true);
  21. while (true) {}
  22. }
  23. // The naked calling convention makes a function not have any function prologue or epilogue.
  24. // This can be useful when integrating with assembly.
  25. fn _start() callconv(.Naked) noreturn {
  26. abort();
  27. }
  28. // The inline calling convention forces a function to be inlined at all call sites.
  29. // If the function cannot be inlined, it is a compile-time error.
  30. fn shiftLeftOne(a: u32) callconv(.Inline) u32 {
  31. return a << 1;
  32. }
  33. // The pub specifier allows the function to be visible when importing.
  34. // Another file can use @import and call sub2
  35. pub fn sub2(a: i8, b: i8) i8 { return a - b; }
  36. // Functions can be used as values and are equivalent to pointers.
  37. const call2_op = fn (a: i8, b: i8) i8;
  38. fn do_op(fn_call: call2_op, op1: i8, op2: i8) i8 {
  39. return fn_call(op1, op2);
  40. }
  41. test "function" {
  42. try expect(do_op(add, 5, 6) == 11);
  43. try expect(do_op(sub2, 5, 6) == -1);
  44. }
  1. $ zig test functions.zig
  2. Test [1/1] test "function"...
  3. All 1 tests passed.

Function values are like pointers:

test.zig

  1. const assert = @import("std").debug.assert;
  2. comptime {
  3. assert(@TypeOf(foo) == fn()void);
  4. assert(@sizeOf(fn()void) == @sizeOf(?fn()void));
  5. }
  6. fn foo() void { }
  1. $ zig build-obj test.zig

Pass-by-value Parameters

Primitive types such as Integers and Floats passed as parameters are copied, and then the copy is available in the function body. This is called “passing by value”. Copying a primitive type is essentially free and typically involves nothing more than setting a register.

Structs, unions, and arrays can sometimes be more efficiently passed as a reference, since a copy could be arbitrarily expensive depending on the size. When these types are passed as parameters, Zig may choose to copy and pass by value, or pass by reference, whichever way Zig decides will be faster. This is made possible, in part, by the fact that parameters are immutable.

test.zig

  1. const Point = struct {
  2. x: i32,
  3. y: i32,
  4. };
  5. fn foo(point: Point) i32 {
  6. // Here, `point` could be a reference, or a copy. The function body
  7. // can ignore the difference and treat it as a value. Be very careful
  8. // taking the address of the parameter - it should be treated as if
  9. // the address will become invalid when the function returns.
  10. return point.x + point.y;
  11. }
  12. const expect = @import("std").testing.expect;
  13. test "pass struct to function" {
  14. try expect(foo(Point{ .x = 1, .y = 2 }) == 3);
  15. }
  1. $ zig test test.zig
  2. Test [1/1] test "pass struct to function"...
  3. All 1 tests passed.

For extern functions, Zig follows the C ABI for passing structs and unions by value.

Function Parameter Type Inference

Function parameters can be declared with anytype in place of the type. In this case the parameter types will be inferred when the function is called. Use @TypeOf and @typeInfo to get information about the inferred type.

test.zig

  1. const expect = @import("std").testing.expect;
  2. fn addFortyTwo(x: anytype) @TypeOf(x) {
  3. return x + 42;
  4. }
  5. test "fn type inference" {
  6. try expect(addFortyTwo(1) == 43);
  7. try expect(@TypeOf(addFortyTwo(1)) == comptime_int);
  8. var y: i64 = 2;
  9. try expect(addFortyTwo(y) == 44);
  10. try expect(@TypeOf(addFortyTwo(y)) == i64);
  11. }
  1. $ zig test test.zig
  2. Test [1/1] test "fn type inference"...
  3. All 1 tests passed.

Function Reflection

test.zig

  1. const expect = @import("std").testing.expect;
  2. test "fn reflection" {
  3. try expect(@typeInfo(@TypeOf(expect)).Fn.args[0].arg_type.? == bool);
  4. try expect(@typeInfo(@TypeOf(expect)).Fn.is_var_args == false);
  5. }
  1. $ zig test test.zig
  2. Test [1/1] test "fn reflection"...
  3. All 1 tests passed.