Learning Zig, day #1

brharrelldev

Brandon Harrell

Posted on June 5, 2024

Learning Zig, day #1

The why

So I have decided to learn Zig. This is kind of an odd decision because I have never had any interest in Zig. But I have had an interest in Rust for years. Just a little about my background. I am a professional Go developer (well was I've been unemployed for 2 weeks). And during my downtime I decided to pick up a new programming language. This will be the 4th professional language I've learned. My first professional language being Java, then Python years later, now Go for the last 8 years.

Toy Program to get started

Anyway I plan writing my own message broker. And for no reason other to see if I can. One of my favorite simple things to write in Go are TCP sockets. So figuring out how to do that in Zig was a priority, surprisingly it's not too bad.

So first they suggest that I create a new Ip4Address. Here it is below.

    const addr = net.Ip4Address.parse("127.0.0.1", 3000);
Enter fullscreen mode Exit fullscreen mode

Alright not too painful. I then have to initialize an Address. This is a struct that takes in an Ip4Address, which I created above. So my next step kind of looks like this.

   var server =  try local_addr.listen(.{.reuse_port=true});
   defer server.deinit();
Enter fullscreen mode Exit fullscreen mode

So what's happening here is pretty cool. We call the "listen" method. Because this can return an error, we call it with "try". This looks suspiciously like an exception. But not to worry, errors are values in Zig, just like Golang. This can alternatively be written like this.

var server =  local_addr.listen(.{.reuse_port=true}) catch |err|{
       //handle value
 };
 defer server.deinit();
Enter fullscreen mode Exit fullscreen mode

So the try semantic is kind of syntactic sugar. Since I'm new to the language and this was my first day using it, I just went with try, but I can see using the "catch" version for better debugging and logging.

Now finally I'm ready to accept a connection, so lets start our loop.

    while(true){
        const conn = try server.accept();
        const message = try conn.stream.reader().readAllAlloc(reader_allocator, 1024);

        print("{s}", .{message});
        reader_allocator.free(message);   
    }

Enter fullscreen mode Exit fullscreen mode

As you can see, we use the "try" syntactic sugar again. Then we obviously read from our connection. What's important is the "readAllAlloc" call. It takes an allocator, and a size which is the size of the allocator. I actually define an "allocator" earlier. I won't get too much into allocators because I only have a super basic understanding of them. But essentially instead of the ownership and borrow checker model found in Rust, we instead can create an allocator structure. We can clean it up whenever we want, but it's usually done via a defer. I'm in a an infinite loop, meaning this will never reach the end of the function. So I an deallocating manually with reader_allocator.free(message).

Here is the fully code:

const std = @import("std");
const net = std.net;
const print = std.debug.print;

pub fn main() !void {

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};

    defer _ = gpa.deinit();

    const reader_allocator = gpa.allocator();

    const addr = try net.Ip4Address.parse("127.0.0.1", 3000);
    const local_addr = net.Address{.in = addr};

    var server =  try local_addr.listen(.{.reuse_port=true});
    defer server.deinit();

    while(true){
        const conn = try server.accept();
        const message = try conn.stream.reader().readAllAlloc(reader_allocator, 1024);

        print("{s}", .{message});
        reader_allocator.free(message);   
    }

}
Enter fullscreen mode Exit fullscreen mode

Thoughts and review so far

So playing around with Zig is actually pretty fun. I've had a lot of false starts with Rust, and it's just hard to get started with. But Zig I was able to just start writing code right away. Zig, while simple, does have a learning curve. But it seems to not be wearing its concepts on its sleeve like Rust. I also think the allocator strategy is genius. And while the Rust borrow checker is also an incredibly good idea, it can make the ownership semantics extremely difficult to work with.

Clearly my program will need some improvements. While this compiles and works, I'm not sending anything back to the client. I also need to build a client as well. Probably put the server in its own package. And I need to figure out what to do with this allocator, because calling it in a loop probably isn't incredibly safe. So I may need to free up memory based on a few conditions, like after a response to the client.

Anyway it's not everyday I'm excited about a new language.

💖 💪 🙅 🚩
brharrelldev
Brandon Harrell

Posted on June 5, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Learning Zig, day #1
zig Learning Zig, day #1

June 5, 2024