View Source Nif options

Zigler gives you several ways to configure compilation options for nifs. These options are part of the use Zig directive, in the keyword options under the key :nifs. This key itself points to a keyword list where the keys are the names of the nif functions and the values are a keyword list of options that apply to that function.

This guide shows you all of the nif options except for options related to Resources, C integration, or Concurrency.

Automatic options (elixir)

To declare that functions should have their options automatically determined, use ... in the nifs parameter list. Nifs which sholud have manually decided options should come after the ... as a keyword list. If all options are automatically determined, then omitting the :nif keyword completely is valid.

defmodule AutomaticOptions do
  use Zig, 
    otp_app: :zigler,
    nifs: [...]

  ~Z"""
  pub fn noop() void {}
  """
end

Automatic options (erlang)

Erlang cannot interpret the ... AST in elixir, so you must use [auto] atom in the nifs options instead.

-zig_opts([{nifs, [auto]}])

Raw calls

Raw calls can be signified with the raw option; this option takes the arity of the function as the value associated with the key.

multiple arities

Currently, multiple arities are not supported, but multiple arities will be supported in a future release.

The normal header for a BEAM nif is as follows:

static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])

In order to support usage of BEAM nifs in the most flexible fashion, you may write your nifs as raw nifs, which looks as follows:

defmodule RawCallTest do
  use ExUnit.Case, async: true

  use Zig, 
    otp_app: :zigler,
    nifs: [
        raw_call_beam: [raw: 1],
        raw_call_erl_nif: [raw: 1]
    ]

  ~Z"""
  const beam = @import("beam");
  const e = @import("erl_nif");

  pub fn raw_call_beam(env: beam.env, count: c_int, list: [*]const beam.term) beam.term {
      return beam.make(env, .{.count = count, .item = list[0]}, .{});
  }

  pub fn raw_call_erl_nif(env: beam.env, count: c_int, list: [*]const e.ErlNifTerm) e.ErlNifTerm {
      return beam.make(env, .{.count = count, .item = beam.term{.v = list[0]}}, .{}).v;
  }
  """

  test "raw call with beam package" do
    assert %{count: 1, item: {:foo, "bar"}} = raw_call_beam({:foo, "bar"})
  end

  test "raw call with erl_nif package" do
    assert %{count: 1, item: {:foo, "bar"}} = raw_call_erl_nif({:foo, "bar"})
  end
end

Note that either the beam.term or the e.ErlNifTerm forms can be used.

Return type

In Collections we saw how certain collection types could be manually marshalled into alternative representations using beam.make. This can be handled as a nif configuration as follows. The advantage to doing it this way is that the typespec for the function will correctly reflect the return type.

defmodule ReturnTypeTest do
  use ExUnit.Case, async: true

  use Zig, 
    otp_app: :zigler,
    nifs: [
        returns_binary: [return: :binary],
        returns_charlist: [return: :list]
    ]

  ~Z"""
  pub fn returns_binary() [3]u16 {
      return [3]u16{47, 48, 49};
  }

  pub fn returns_charlist() []const u8 {
      return "Hello world!";
  }
  """

  test "returns binary" do
    assert <<47, 0, 48, 0, 49, 0>> = returns_binary()
  end

  test "returns charlist" do
    assert ~C'Hello world!' = returns_charlist()
  end
end

Alias

It's possible to create a new function which is an alias of another function. This is how it's done:

defmodule AliasTest do
  use ExUnit.Case, async: true

  use Zig, 
    otp_app: :zigler,
    nifs: [
      ...,
      new_function: [alias: :old_function]
    ]

  ~Z"""
  pub fn old_function() u32 {
      return 47;
  }
  """

  test "both main and alias functions work" do
    assert 47 == old_function()
    assert 47 == new_function()
  end
end

Args options

Arguments can also take options, using args: [...]

Noclean

If you want to disable automatic allocator cleanup of datatypes, you can do so either in the :return or :args section by including the :noclean option.
This is most useful if your args or return data are going to be persisted beyond the lifetime of the nif call, though doing this in many cases is not recommended.

This flag can be stacked with previous options, for example:
return: [:noclean, :binary].

Leak Check

It's possible to wrap each function call in its own instance of beam.general_purpose_allocator bound into the beam.allocator threadlocal variable. If you tag your nif as leak_check, it will check that beam.allocator has cleared all of its contents at the end of the function call, and if that hasn't happened, it raises.

leak check warning

leak check doesn't seem to be working in 0.11.0 and will return in 0.11.1

defmodule LeakCheckTest do
  use ExUnit.Case, async: true

  use Zig,
    otp_app: :zigler,
    nifs: [check_me: [leak_check: true]]

  ~Z"""
  const beam = @import("beam");
  pub fn check_me() !void {
      _ = try beam.allocator.create(u8);
  }
  """

  @tag :skip
  test "leak check" do
    assert_raise RuntimeError, "memory leak detected in function `check_me/0`", fn ->
      check_me()
    end
  end
end

leak_check can also be applied to all nifs in the module:

defmodule LeakCheckAllTest do
  use ExUnit.Case, async: true

  use Zig,
    otp_app: :zigler,
    leak_check: true

  ~Z"""
  const beam = @import("beam");
  pub fn check_me() !void {
      _ = try beam.allocator.create(u8);
  }
  """

  @tag :skip
  test "leak check" do
    assert_raise RuntimeError, "memory leak detected in function `check_me/0`", fn ->
      check_me()
    end
  end
end

conditional leak checks

It's not currently possible to make conditional leak checks, but this will be fixed in the future.

Typespec override (Elixir-only)

Typespecs can be overridden by using the spec option, this syntax should match that of a normal typespec. Note that overriding the autogenerated typespec is the only way to specify the type of function which uses beam.term in its arguments or return.

For example:

defmodule Override do
  use Zig, 
    otp_app: :zigler,
    nifs: [typespec_override: [spec: (integer -> integer)]]

  ~Z"""
  const beam = @import("beam");
  pub fn typespec_override(env: beam.env, term: beam.term) !beam.term {
      const input = try beam.get(u32, env, term, .{});
      return beam.make(env, input + 1, .{});
  }
  """
end

Disable documentation

Documentation can be disabled with the docs: false option.

defmodule DisableDoc do
  use Zig, 
    otp_app: :zigler,
    nifs: [nodocs: [docs: false]]

  ~Z"""
  pub fn nodocs() void {}
  """
end

Ignore

Public functions can be ignored and not converted into nifs by filling out the :ignore option in use Zig directive.

defmodule IgnoreTest do
  use ExUnit.Case, async: true

  use Zig, 
    otp_app: :zigler,
    ignore: [:ignored]

  ~Z"""
  pub fn ignored(number: u32) u32 {
      return number + 1;
  }

  pub fn available(number: u32) u32 {
      return ignored(number);
  }
  """

  test "available function works" do
    assert 48 = available(47)
  end

  test "ignored function is not available" do
    refute function_exported?(__MODULE__, :ignored, 0)
  end
end
#module