Seth Barrett

Daily Blog Post: September 6th, 2023

Zig

September 6th, 2023

Part 5: Functions and Procedures in Zig

Welcome to the fifth installment of our "Getting Started with Zig on MacOS" series. In this part, we'll explore the world of functions and procedures in Zig. Functions are essential for organizing your code into reusable blocks, and they play a crucial role in achieving modularity and code maintainability.

Declaring and Using Functions

Declaring Functions

In Zig, you declare functions using the fn keyword, followed by the function name and its signature. Here's a simple example of a function that adds two numbers:

fn add(a: i32, b: i32) i32 {
    return a + b;
}

In this example:

  • fn is used to declare a function.
  • add is the function name.
  • (a: i32, b: i32) is the parameter list with their data types.
  • i32 after the function signature specifies the return type, which is a 32-bit signed integer in this case.

Calling Functions

To use a function, you call it with the appropriate arguments and handle the returned value:

const result = add(3, 4);

In this example, result will contain the value 7, which is the result of adding 3 and 4 using the add function.

Function Arguments and Return Values

Function Arguments

Zig allows you to define functions with multiple parameters, including different data types. For instance, you can declare a function that calculates the area of a rectangle:

fn calculateArea(length: f64, width: f64) f64 {
    return length * width;
}

Function Return Values

Functions can return values of any data type, including custom types and structs. The return type is specified after the parameter list. Here's an example of a function returning a custom struct:

const std = @import("std");

const MyStruct = struct {
    x: i32,
    y: i32,
};

fn createMyStruct(a: i32, b: i32) MyStruct {
    return MyStruct{ .x = a, .y = b };
}

Procedures and Error Handling

Procedures

In Zig, procedures are functions that don't return a value. You declare procedures similarly to functions but with the comptime keyword to indicate they don't return a result:

comptime fn logMessage(message: []const u8) void {
    // This is a procedure that logs a message but doesn't return anything
    std.debug.print("Log: {}\n", .{message});
}

Error Handling

Zig provides a powerful error handling mechanism using error sets. You can return error sets from functions and use try to handle errors gracefully:

const std = @import("std");

const Error = error{ErrorReason};
const ErrorReason = enum {
    OutOfBounds,
    InvalidInput,
};

fn divide(a: i32, b: i32) !f64 {
    if (b == 0) {
        return Error.OutOfBounds;
    }
    if (a < 0) {
        return Error.InvalidInput;
    }
    return f64(a) / f64(b);
}

const result = try divide(10, 2);
if (result == Error.OutOfBounds) {
    std.debug.print("Error: Division by zero\n");
} else if (result == Error.InvalidInput) {
    std.debug.print("Error: Invalid input\n");
} else {
    std.debug.print("Result: {}\n", .{result});
}

In this example, the divide function can return two different error reasons if certain conditions are met. The try keyword is used to handle these errors gracefully.

What's Next?

In this part, you've learned how to declare and use functions and procedures in Zig, including function arguments, return values, and error handling. Functions and procedures are essential building blocks for creating modular and maintainable code.

In Part 6, we'll dive into data structures in Zig, exploring arrays, slices, structures (structs), enums, and tagged unions. These data structures will allow you to organize and manipulate data effectively in your programs. Stay tuned for more Zig insights and practical examples. Happy coding!