Skip to main content
  1. Posts/

Zig Juice Has Arrived in 0.16.0

·3 mins
definitepotato
Author
definitepotato
Potato quality writing. Documenting my many failures, celebrating my success, sharing my passions, ideas and musings.
Table of Contents

This juicy merge answers, what if you could receive a bunch of goodies in main simply by asking for them? As a result, there are now 3 ways to define the main function.

pub fn main() !void { ... }
pub fn main(init: std.process.Init) !void { ... }
pub fn main(init: std.process.Init.Minimal) !void { ... }

The structure to understand is Init. This struct is now the only way to access environment variables and cli arguments, os.environ and os.argv have been deleted.

First, Let’s talk about Minimal Init
#

The new method of getting command line arguments.

pub fn main(init: std.process.Init.Minimal) void {
  // Use an allocator of your choice.
  var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
  defer _ = debug_allocator.deinit();
  const gpa = debug_allocator.allocator();

  var i: u32 = 0;
  var args = try init.args.iterateAllocator(gpa);
  args.deinit();
  while (args.next()) |arg| : (i += 1) {
    std.debug.print("arg[{d}]={s}\n", .{i, arg});
  }
}

You can also use toSlice() but an arena allocator must be used.

pub fn main(init: std.process.Init.Minimal) void {
  var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
  defer arena_allocator.deinit();
  const arena = arena_allocator.allocator();

  const args = try init.args.toSlice(arena);
  for (args) |arg| {
    std.debug.print("arg: {s}\n", .{arg});
  }
}

How do we access environment fields?
#

The difference between the constant and non-constant versions of environ is that the non-constant versions create an environ map std.process.Environ.Map.

pub fn main(init: std.process.Init.Minimal) !void {
  var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
  defer arena_allocator.deinit();
  const arena = arena_allocator.allocator();

  const contains_home = try init.environ.contains(arena, "HOME");
  const home_is_unempty = try init.environ.containsUnempty(arena, "HOME");
  const contains_editor = init.environ.containsConstant("EDITOR");
  const editor_is_unempty = init.environ.containsUnemptyConstant("EDITOR");
  std.debug.print("{} {} {} {}", .{contains_home, home_is_unempty, contains_editor, editor_is_unempty});

  // Get a specific environ value.
  std.debug.print("EDITOR: {s}\n", .{try init.environ.getAlloc(arena, "EDITOR")});
  if (builtin.os.tag == .windows) {
    std.debug.print("HOME: {?s}\n", .{init.environ.getWindows("HOME")});
  } else if (builtin.os.tag != .wasi or builtin.link_libc) {
    std.debug.print("HOME: {?s}\n", .{init.environ.getPosix("HOME")});
  }

  // Iterate/Find specific environ values as key/value pairs of a map.
  const environ_map = try init.environ.createMap(arena);
  for (environ_map.keys(), environ_map.values()) |key, value| {
    std.debug.print("env: {s}={s}\n", .{key, value});
  }
}

Let’s talk about Init (non-Minimal) …
#

The non-Minimal Init is a superset of Minimal

pub const Init = struct {
  /// Init is a superset of Minimal.
  minimal: Minimal,
  /// Permanent storage for the entire process, cleaned automatically on exit.
  arena: *std.heap.ArenaAllocator,
  /// A default-selected general purpose allocator for temporary heap allocations.
  /// Debug mode will set  up leak checking.
  gpa: std.mem.Allocator,
  /// An appropriate default Io implementation based on the target configuration.
  /// Debug mode will set up leak checking.
  io: std.Io,
  /// Environment variables, initialized with gpa. Not threadsafe.
  environ_map: *Environ.Map,
  /// Named files that have been provded by the parent process. This is mainly useful on WASI, but can be used on other systems to mimic the behavior with respect to stdio.
  propens: Propens,
};

Notes about Init:

We can use all our previous code but instead of calling init.environ.contains we’d call init.minimal.environ.contains.

Init comes with an environ_map so we don’t need to create one, we can use the existing one.

pub fn main(init: std.process.Init) !void {
    for (init.environ_map.keys(), init.environ_map.values()) |key, value| {
        std.log.info("env: {s}={s}", .{ key, value });
    }
}

It comes with an Arena and Debug allocator. You can plug them into your application, no need to call deinit().

I’ve captured a summary above, for more in-depth information check out this video by ComputerBread.

Related