View Source beam (zigler v0.13.3)

This struct contains adapters designed to facilitate interfacing the BEAM's c-style helpers for NIFs with a more idiomatic Zig-style of programming, for example, the use of slices instead of null-terminated arrays as strings.

This struct derives from priv/beam/beam.zig, and is provided to the project as a package. You may import it into any project zig code using the following code:

const beam = @import("beam")

If there's something in the BEAM nif API you need which is not provided, you can also import erl_nif package which provides direct access to the equivalent calls from erl_nif.h This can be done with the following code:

const e = @import("erl_nif");

Summary

Functions (Concurrency)

periodic check-in function for long-running nifs.

Functions (Context)

convenience function to get the context's execution environment

Functions (Env)

Performs e.enif_clear_env on the existing env in the context. This function is generally called by beam.send, but is provided if you need to manually trigger environment clearing. The function is also passed a tuple which is a list of terms that should be persisted into the new environment.

copies a term from one env to to another

creates an new independent environment and attaches it to the existing context. You may specify the allocator option to set the default allocator for the context. Defaults to beam.allocator.

Functions (Term Management)

converts a []u8 to a term/0. The binary must be encoded using erlang term format.

generic cleanup function that can be used to cleanup values that have been created by get.

compares two terms, following Elixir's compare interface.

converts BEAM term dynamic types into static zig types

converts static zig types into BEAM term dynamic types

returns the empty list term [].

shortcut for make(.@"error", .{})

shortcut for make(env, .{.@"error", value}, options)

turns a []const u8 into a the corresponding atom/0 term.

performs a list cons operation for head and tail variables

causes the VM to generate a new reference term equivalent to Kernel.make_ref/0

converts a zig std.builtin.StackTrace into a special term that is designed to be translated and concatenated onto a BEAM stacktrace.

marks a e.ErlNifBinary as qualified to be garbage collected. This is a thin wrapper over e.enif_release_binary.

returns a pid value that represents the current or parent process.

sends data (as a term) to a target process' mailbox.

converts a term/0 to a e.ErlNifBinary using erlang term format serialization.

Types

result type for compare

a datastructure containing information that is local to the current call. managed entrypoints into any given nif (any nif that isn't raw) will set have access to the threadlocal context/0 variable.

a tag identifying the type of context in which the nif is running

A zig enum equivalent to e.ErlNifTermType

env

identical to ?*e.ErlNifEnv

identical to e.ErlNifEvent. This is an event datatype that the BEAM documentation does not describe.

boilerplate functions for nif module initialization. Contains

identical to e.ErlNifMonitor. This is a monitor datatype that the BEAM documentation does not describe.

pid

identical to ?*e.ErlNifPid

identical to ?*e.ErlNifPort

wrapped term.

tid

identical to e.ErlNifTid. This is a thread id datatype that the BEAM. documentation does not describe.

Constants

wraps e.enif_alloc and e.enif_free into the zig standard library allocator interface.

implements std.mem.Allocator using the std.mem.GeneralPurposeAllocator factory, backed by beam.wide_alignment_allocator.

provides a BEAM allocator that can perform allocations with greater alignment than the machine word.

Variables

threadlocal variable that stores the execution context for the nif. Execution of any managed (non-raw) nif is guaranteed to have access to this variable, and the value of this variable will not be changed across any reentries into the function (for example, for yielding nifs)

Functions (Exceptions)

Link to this nil

raise_elixir_exception(module: []u8, data: anytype, opts: anytype) term

View Source
@spec raise_elixir_exception([]const u8, anytype, anytype) :: term

The equivalent of Kernel.raise/1 from elixir.

  • module should be the name of the module that represents the exception
  • data should be a struct (possibly anonymous) that represents the Elixir exception payload.

Example

~Z"""
pub fn raise_example() beam.term {
     return beam.raise_elixir_exception(
         "CompileError",
         .{.file = "foo", .line = 42, .description = "something went wrong"},
         .{});
}
"""

test "raise_elixir_execption" do
   assert_raise CompileError, "foo:42: something went wrong", fn ->
     raise_example()
   end
end

Exception structs are not type checked

Unlike code in elixir, the validity of the exception struct is not checked when using this function.

Link to this nil

raise_exception(reason: anytype, opts: anytype) term

View Source
@spec raise_exception(anytype, anytype) :: term

The equivalent of error in erlang.

special-cased exception terms

N.B. certain erlang terms are caught and handled as special cases in elixir, see the following example:

Example

~Z"""
pub fn raise_exception_example() beam.term {
     return beam.raise_exception(.badarg, .{});
}
"""

test "raise_exception" do
   assert_raise ArgumentError, fn ->
     raise_exception_example()
   end
end
Link to this nil

raise_with_error_return(err: anytype, maybe_return_trace: std.builtin.StackTrace, opts: anytype) term

View Source
@spec raise_with_error_return(anytype, ?*std.builtin.StackTrace, anytype) :: term

Raises with a special error datatype that contains an term-encoded stacktrace datastructure. See also make_stacktrace

This datastructure is designed to be concatenated onto the existing stacktrace. In order to concatenate this stacktrace onto your BEAM exception, the function that wraps the nif must be able to catch the error and append the zig error return trace to the existing stacktrace.

Functions (Concurrency)

@spec yield() :: !void

periodic check-in function for long-running nifs.

always returns error.processterminated if the calling process has been terminated.

Note that the environment of the function may be invalid so if you must send information back to the BEAM on a process termination event, you should have a beam environment allocated to communicate with the BEAM using send

For yielding nifs (not implemented yet):

  • relinquishes control to the BEAM scheduler.
  • If the thread's parent process has been killed, returns error.processterminated
  • when control is returned from the scheduler, resumes with no error.
  • creates an async suspend point.

Example

~Z"""
pub fn yielding_example(pid: beam.pid) !void {
     // for this to work, we must have an independent
     // environment, as the process environment will have been
     // destroyed when the beam.yield error return occurs.
     const env = beam.alloc_env();
     defer beam.free_env(env);

     while (true) {
        std.time.sleep(100000);
        beam.yield() catch {
           try beam.send(pid, .died, .{.env = env});
           return;
        };
     }
}
"""

test "yield" do
   test_pid = self()
   spawn_pid = spawn(fn -> yielding_example(test_pid) end)
   Process.sleep(100)
   Process.exit(spawn_pid, :kill)
   assert_receive :died, 1000
end

Functions (Context)

@spec get_env() :: env

convenience function to get the context's execution environment

Functions (Env)

@spec alloc_env() :: env

Synonym for e.enif_alloc_env

generally, zigler takes care of environments for you, but there may be cases where you have to manually allocate an environment. These are process independent environments which manage their own heaps.

use your own process independent environment, most beam functions can take a .env option which lets you specify the environment; these functions generally default to the context's environment (beam.context.env).

pair with free_env

all calls to alloc_env/0 should be balanced with a call to free_env/1.

~Z"""
pub fn alloc_env_example(pid: beam.pid) !void {
     const env = beam.alloc_env();
     defer beam.free_env(env);

     try beam.send(pid, .{.ok, 47}, .{.env = env});
}
"""

test "alloc_env_example" do
   alloc_env_example(self())
   assert_receive {:ok, 47}
end
Link to this nil

clear_env(env_: env, persist: anytype) ClearEnvReturn(@TypeOf(persist))

View Source
@spec clear_env(env, anytype) :: anytype

Performs e.enif_clear_env on the existing env in the context. This function is generally called by beam.send, but is provided if you need to manually trigger environment clearing. The function is also passed a tuple which is a list of terms that should be persisted into the new environment.

This function will panic if the context's environment is not one known to be created by alloc_env.

returns void if the persist list is empty; otherwise returns a tuple of the same size as the persist list, contaning the terms that are persisted, in the same order.

Example

~Z"""
pub fn clear_env_example(pid: beam.pid, term: beam.term) !void {
    const env = beam.alloc_env();
    defer beam.free_env(env);

    const inner_term = beam.copy(env, term);
    try beam.send(pid, .{.first, term}, .{.env = env, .clear = false});

    // this function call is required since we did not clear our env
    // after sending the term in the previous call.

    const new_term = beam.clear_env(env, .{inner_term});
    try beam.send(pid, .{.second, new_term}, .{.env = env, .clear = false});

    return;
}
"""

test "clear_env" do
   clear_env_example(self(), 47)
   assert_receive {:first, 47}
   assert_receive {:second, 47}
end
Link to this nil

copy(env_: env, term_: term) term

View Source
@spec copy(env, term) :: term

copies a term from one env to to another

@spec free_env(env) :: void

Synonym for e.enif_free_env

See alloc_env/0 for more information.

context env warning

unless you have created an independent context using independent_context/1, do not use this function to free the beam.context.env, as unexpected results may occur.

Link to this nil

independent_context(opts: InitEnvOpts) void

View Source
@spec independent_context(beam.InitEnvOpts) :: void

creates an new independent environment and attaches it to the existing context. You may specify the allocator option to set the default allocator for the context. Defaults to beam.allocator.

~Z"""
pub fn independent_context_example(pid: beam.pid) !void {
    beam.independent_context(.{});
    try beam.send(pid, beam.context.mode, .{});
    beam.free_env(beam.context.env);
}
"""

test "independent contexts" do
   independent_context_example(self())
   assert_receive :independent
end

free independent context envs

you must ALWAYS free beam.context.env if you have created an independent context.

Functions (Term Management)

Link to this nil

binary_to_slice(e.ErlNifBinary) []u8

View Source
@spec binary_to_slice(e.ErlNifBinary) :: []u8

converts a e.ErlNifBinary to []const u8.

Does not perform allocations or copies

Example

~Z"""
pub fn binary_to_slice_example(term: beam.term) u8 {
   const e = @import("erl_nif");
   var binary: e.ErlNifBinary = undefined;
   _ = e.enif_inspect_binary(beam.context.env, term.v, &binary);
   const slice = beam.binary_to_slice(binary);
   return slice[2];
}
"""

test "binary_to_slice" do
   assert ?o = binary_to_slice_example("foo")
end

binary data

This points to the data location of the binary, which might either be in the shared binary heap, or it might be in the process heap (for small binaries). This should be considered read-only, attempts to sneakily modify these data will have undefined effects, possibly including broken comparison operations.

Link to this nil

binary_to_term(encoded_term: []const u8, options: anytype) !term

View Source
@spec binary_to_term([]const u8, anytype) :: !term

converts a []u8 to a term/0. The binary must be encoded using erlang term format.

This is a thin wrapper over e.enif_binary_to_term.

Example

~Z"""
pub fn binary_to_term_example(encoded_number: []const u8) !u32 {
   const term = try beam.binary_to_term(encoded_number, .{});
   const number = try beam.get(u32, term, .{});
   return number + 1;
}
"""

test "binary_to_term" do
   assert 48 = binary_to_term_example(:erlang.term_to_binary(47))
end
Link to this nil

cleanup(value: anytype, options: anytype) void

View Source
@spec cleanup(anytype, anytype) :: void

generic cleanup function that can be used to cleanup values that have been created by get.

The second parameter is an options parameters, which should be passed a struct (possibly anonymous) with the following fields:

  • allocator: which allocator should be used to clean up allocations. optional, defaults to the threadlocal beam.context.allocator value
  • size: if the value does not have a defined size, for example a Many pointer, you must provide this value.

noops on terms that do not require cleanup.

Example

~Z"""
const CleanupErrors = error{leaked};

pub fn cleanup_example(term: beam.term) !i32 {
     var gpa = beam.make_general_purpose_allocator_instance();
     const allocator = gpa.allocator();
     const slice = try beam.get([]i32, term, .{.allocator = allocator});
     const number = slice[0];
     beam.cleanup(slice, .{.allocator = allocator});
     if (gpa.detectLeaks()) return error.leaked;
     return number + 1;
}
"""

test "cleanup" do
   assert 48 = cleanup_example([47])
end
Link to this nil

compare(lhs: term, rhs: term) Compared

View Source
@spec compare(term, term) :: beam.Compared

compares two terms, following Elixir's compare interface.

Example

~Z"""
pub fn compare_example(lhs: beam.term, rhs: beam.term) beam.Compared {
     return beam.compare(lhs, rhs);
}
"""

test "beam.compare" do
   assert :gt == compare_example(:infinity, 47)
   assert :eq == compare_example(self(), self())
   assert :lt == compare_example(:atom, "string")
end
Link to this nil

get(dest_type: T, source_term: beam.term, options: anytype) T

View Source
@spec get(T, beam.term, anytype) :: T

converts BEAM term dynamic types into static zig types

The arguments are as follows:

  1. destination type
  2. term to convert
  3. struct (usually passed as anonymous) of keyword options for additional features. See supported options

See also make for the reverse operation.

The following type classes (as passed as 1st argument) are supported by get:

integer

  • unsigned and signed integers are supported
  • all integer sizes from 0..64 bits are supported (including non-power-of-2 sizes)
  • for sizes bigger than 64, are supported, but the passed term must be a native-endian binary with size n*64 bits

Example

~Z"""
pub fn get_integer_example(term: beam.term) !i32 {
     const x: i32 = try beam.get(i32, term, .{});
     return x + 1;
}

// NOTE: the size of the integer in the following function
pub fn get_big_integer_example(term: beam.term) !u65 {
   const x: u65 = try beam.get(u65, term, .{});
   return x + 1;
}
"""

test "get small integer" do
   assert 48 = get_integer_example(47)
end

test "get big integer" do
   # note we must pass as a binary, with width 128 bits.
   assert 0x1_0000_0000_0000_0000 =
     get_big_integer_example(
       <<0xffff_ffff_ffff_ffff :: native-unsigned-64, 0::64>>
     )
end

enum

  • may be passed the integer value of the enum.
  • may be passed the atom representation of the enum.
  • zero- and one- item enum types are not currently supported

Example

~Z"""
const EnumType = enum {foo, bar};
pub fn get_enum_example(term: beam.term) !EnumType {
     return try beam.get(EnumType, term, .{});
}
"""

test "get integer enum" do
   assert :foo = get_enum_example(0)
end

test "get atom enum" do
   assert :foo = get_enum_example(:foo)
end

float

  • supports f16, f32, and f64 types.
  • may be passed a BEAM float/0 term
  • atoms :infinity, :neg_infinity, :NaN are also supported

Example

~Z"""
const NanError = error {nanerror};

pub fn get_float_example(term: beam.term) !f32 {
     const x: f32 = try beam.get(f32, term, .{});
     if (!std.math.isNan(x)) {
          return x + 1.0;
     } else {
          return error.nanerror;
     }
}
"""

test "get real float" do
   assert 48.0 = get_float_example(47.0)
end

test "get infinite float" do
   assert :infinity = get_float_example(:infinity)
   assert :neg_infinity = get_float_example(:neg_infinity)
end

test "get nan float" do
   assert_raise ErlangError, fn -> get_float_example(:NaN) end
end

struct

  • may be passed map/0 with atom/0 keys and values of the appropriate type
  • may be passed a keyword/0 list with atom/0 keys and values of the appropriate type.
  • inner values are recursively marshalled to to the appropriate type.
  • if the struct is packed or extern, supports binary data.

Example

~Z"""
const MathType = packed struct {
     left: packed struct{ value: u32 },
     right: packed struct{ value: u32 },
     op: enum(u8) {add, sub}
};

pub fn get_struct_example(term: beam.term) !u32 {
    const operation = try beam.get(MathType, term, .{});
    switch (operation.op) {
       .add => return operation.left.value + operation.right.value,
       .sub => return operation.left.value - operation.right.value,
    }
}
"""

test "get struct" do
   assert 49 = get_struct_example(%{
     op: :add,
     left: %{value: 47},
     right: %{value: 2}})

   assert 42 = get_struct_example(%{
     op: :sub,
     left: %{value: 47},
     right: %{value: 5}})
end

test "get struct as packed binary" do
   assert 49 = get_struct_example(<<47::native-32, 2::native-32, 0::8, 0::56>>)
end

packed and extern structs

if you are doing binary encoding of these values, be mindful of native-endianness of the results and the need for padding to conform to alignment requirements.

note that the C ABI specification may reorder your values, whereas a packed struct may have wider than expected alignment.

bool

  • supports true and false boolean/0 terms only.

Example

~Z"""
pub fn get_bool_example(term: beam.term) !bool {
    return try beam.get(bool, term, .{});
}
"""

test "get bool" do
   assert get_bool_example(true)
   refute get_bool_example(false)
end

array

  • supports lists of terms that can be converted to the array's element type.
  • note that arrays have compile-time known length.
  • if the array's element is integers, floats, packed or extern structs, or arrays that support binaries, then the array can be passed binary data.
  • does not perform allocation

    Allocation warning

    as allocation is not performed, getting could be a very expensive operation. these values will be on the stack.

Example

~Z"""
const PackedValue = packed struct { v: u32 };
pub fn get_array_example(term: beam.term) !u32 {
     const array = try beam.get([3]PackedValue, term, .{});
     var sum: u32 = 0;
     for (array) |item| { sum += item.v; }
     return sum;
}
"""

test "get array" do
   assert 144 = get_array_example([%{v: 47}, %{v: 48}, %{v: 49}])
end

test "get array as binary" do
   assert 144 = get_array_example(<<47::native-32, 48::native-32, 49::native-32>>)
end

single-item pointer

  • used if an struct or array must be allocated on the heap.
  • allocates memory based on allocator provided in the options, otherwise defaults to beam.allocator
  • data may be passed as if they were not a pointer.
  • returns an error if the allocation fails.

Allocation warning

it is the caller's responsibility to free the memory allocated by this function, via the allocator.destroy function, not allocator.free

Example

# see previous example cell for type definitions
~Z"""
pub fn get_struct_pointer_example(term: beam.term) !u32 {
     const structptr = try beam.get(*PackedValue, term, .{});
     defer beam.allocator.destroy(structptr);

     return structptr.v;
}

pub fn get_array_pointer_example(term: beam.term) !u32 {
    const arrayptr = try beam.get(*[3]PackedValue, term, .{});
    defer beam.allocator.destroy(arrayptr);

    var sum: u32 = 0;
    for (arrayptr.*) |item| { sum += item.v; }
    return sum;
}
"""

test "get struct pointer" do
   assert 47 = get_struct_pointer_example(%{v: 47})
end

test "get array pointer" do
   assert 144 = get_array_pointer_example(<<47::native-32, 48::native-32, 49::native-32>>)
end

slice

  • allocates memory based on allocator provided in the options, otherwise defaults to beam.allocator
  • note that slice carries a runtime length
  • supports list of any type
  • supports binary of any type that can be represented as a fixed size binary.

Allocation warning

it is the caller's responsibility to free the memory allocated by this function, via the allocator.free function, not allocator.destroy

Example

~Z"""
pub fn get_slice_example(term: beam.term) !u32 {
    const slice = try beam.get([]packed struct{ v: u32 }, term, .{});
    defer beam.allocator.free(slice);
    var sum: u32 = 0;
    for (slice) |item| { sum += item.v; }
    return sum;
}
"""

test "get slice" do
   assert 144 = get_slice_example([%{v: 47}, %{v: 48}, %{v: 49}])
end

test "get slice as binary" do
   assert 144 = get_slice_example(<<47::native-32, 48::native-32, 49::native-32>>)
end

many-item-pointer

  • allocates memory based on allocator provided in the options, otherwise defaults to beam.allocator
  • supports list of any type
  • supports binary of any type that can be represented as a fixed size binary.
  • the runtime length is not a part of this datastructure, you are expected to keep track of it using some other mechanism. Caller is responsible for tracking the length.

    Length warning

    due to the fact that this datatype drops its length information, this datatype should be handled with extreme care.

  • the allocated runtime length can be retrieved by passing a *usize parameter as the size parameter to the options.

Example

~Z"""
pub fn get_many_item_pointer_example(term: beam.term) !u32 {
   var size: usize = undefined;
   const pointer = try beam.get([*]u32, term, .{.size = &size});
   defer beam.allocator.free(pointer[0..size]);
   var sum: u32 = 0;
   for (0..size) |index| { sum += pointer[index]; }
   return sum;
}
"""

test "get many item pointer" do
   assert 144 = get_many_item_pointer_example([47, 48, 49])
end

test "get many item pointer as binary" do
   assert 144 = get_many_item_pointer_example(<<47::native-32, 48::native-32, 49::native-32>>)
end

cpointer

  • allocates memory based on allocator provided in the options, otherwise defaults to beam.allocator

  • supports list of any type

  • supports binary of any type that can be represented as a fixed size binary.

  • the runtime length is not a part of this datastructure, you are expected to keep track of it using some other mechanism

    Length warning

    due to the fact that this datatype drops its length information, this datatype should only be used where c interop is needed

  • the allocated runtime length can be retrieved by passing a *usize parameter as the size parameter to the options.

Example

~Z"""
pub fn get_c_pointer_example(term: beam.term) !u32 {
   var size: usize = undefined;
   const pointer = try beam.get([*c]u32, term, .{.size = &size});
   defer beam.allocator.free(pointer[0..size]);
   var sum: u32 = 0;
   for (0..size) |index| { sum += pointer[index]; }
   return sum;
}
"""

test "get c pointer" do
   assert 144 = get_c_pointer_example([47, 48, 49])
end

test "get c pointer as binary" do
   assert 144 = get_c_pointer_example(<<47::native-32, 48::native-32, 49::native-32>>)
end

optional

  • accepts atom/0 nil as well as whatever the child type is.

nil vs null

note that zig uses null as its nil value, but zigler only accepts atom nil as its null value.

Example

~Z"""
pub fn get_optional_example(term: beam.term) !u32 {
     const x = try beam.get(?u32, term, .{});
     if (x) |value| { return value + 1; } else { return 47; }
}
"""

test "get optional" do
   assert 48 = get_optional_example(47)
   assert 47 = get_optional_example(nil)
end

pid

Example

~Z"""
pub fn get_pid_example(term: beam.term) !void {
     const pid = try beam.get(beam.pid, term, .{});
     try beam.send(pid, .foo, .{});
}
"""

test "get pid" do
   assert :ok = get_pid_example(self())
   assert_receive :foo
end

Supported options

  • env: required for raw nifs. If not provided, defaults to the context's environment.
  • allocator: the allocator to use for allocations. If not provided, defaults to the context's environment.
  • error_info: pointer to a term that can be populated with error information that gets propagated on failure to convert. If omitted, the code to produce these errors will get optimized out.
  • keep (bool, only applies to references): if true (default) the refcount will be increased on the term as a result of performing the get operation.
  • size (* usize, only applies to manypointer or [*c]):
Link to this nil

make(value: anytype, options: anytype) term

View Source
@spec make(anytype, anytype) :: term

converts static zig types into BEAM term dynamic types

The arguments are as follows:

  • environment
  • value to convert to term
  • options struct (usually passed as anonymous) of keyword options for additional features. See supported options. Note this struct must be comptime-known.

See also get for the reverse operation.

The following zig types are supported:

term

  • no conversion is performed
  • this type is necessary for recursive make operations

Example

~Z"""
pub fn make_term_example(term: beam.term) beam.term {
     return beam.make(term, .{});
}
"""

test "make term" do
   assert 47 = make_term_example(47)
end

void

  • returns atom :ok
  • supporting this type makes metaprogramming easier.

Example

~Z"""
pub fn make_void_example() beam.term {
    const v: void = undefined;
    return beam.make(v, .{});
}
"""
test "make void" do
   assert :ok = make_void_example()
end

pid

~Z"""
pub fn make_pid_example(pid: beam.pid) beam.term {
    return beam.make(pid, .{});
}
"""
test "make pid" do
   assert self() == make_pid_example(self())
end

std.builtin.StackTrace

  • special interface for returning stacktrace info to BEAM.

integers

  • unsigned and signed integers supported
  • all integer sizes from 0..64 bits supported (including non-power-of-2 sizes)
  • returns a BEAM integer/0 term
  • for sizes bigger than 64, supported, but the passed term will be converted into a binary term bearing the integer encoded with native endianness.
  • comptime integers supported

Example

~Z"""
pub fn make_integer_example(integer: u32) beam.term {
   return beam.make(integer + 1, .{});
}

pub fn make_big_integer_example(integer: u65) beam.term {
   return beam.make(integer + 1, .{});
}

pub fn make_comptime_integer_example() beam.term {
   return beam.make(47, .{});
}
"""

test "make integer" do
   assert 48 = make_integer_example(47)
end

test "make big integer" do
   assert <<0::64, 1::64-native>> = make_big_integer_example(0xFFFF_FFFF_FFFF_FFFF)
end

test "make comptime integer" do
   assert 47 = make_comptime_integer_example()
end

floats

  • supports f16, f32, and f64 types.
  • supports comptime float type.
  • returns a BEAM float/0 term
  • may also return one of the atom/0 type :infinity, :neg_infinity, :NaN

Example

~Z"""
pub fn make_float_example(float: f32) beam.term {
   return beam.make(float + 1.0, .{});
}

pub fn make_comptime_float_example() beam.term {
   return beam.make(47.0, .{});
}
"""

test "make float" do
   assert 48.0 = make_float_example(47.0)
   assert :infinity = make_float_example(:infinity)
   assert :neg_infinity = make_float_example(:neg_infinity)
   assert :NaN = make_float_example(:NaN)
end

test "make comptime float" do
   assert 47.0 = make_comptime_float_example()
end

bool

  • supports bool types.
  • returns a BEAM boolean/0 term

Example

~Z"""
pub fn make_bool_example(value: bool) beam.term {
   return beam.make(!value, .{});
}
"""

test "make bool" do
   assert make_bool_example(false)
   refute make_bool_example(true)
end

enum or error enum

  • supports enum or error types.
  • doesn't support zero or one-item enums.
  • returns a BEAM atom/0 term
  • also supports enum literals.

Example

~Z"""
const MakeEnums = enum {foo, bar};
pub fn make_enum_example(value: MakeEnums) beam.term {
     return beam.make(value, .{});
}

pub fn make_enum_as_int_example(value: MakeEnums) beam.term {
     return beam.make(value, .{.as = .integer});
}

const MakeErrorSet = error { MakeEnumError };
pub fn make_error_example() beam.term {
     return beam.make(error.MakeEnumError, .{});
}

pub fn make_enum_literal_example() beam.term {
     return beam.make(.foobarbaz, .{});
}
"""

test "make enum" do
   assert :foo = make_enum_example(:foo)
   assert 0 = make_enum_as_int_example(:foo)
end

test "make error" do
   assert :MakeEnumError = make_error_example()
end

test "make enum literal" do
   assert :foobarbaz = make_enum_literal_example()
end

Enum literals

Enum literals are especially useful for returning atoms, such as :ok or :error. Note that error is a reserved word in zig, so you will need to use .@"error" to generate the corresponding atom. See also make_error_atom

optionals or null

  • supports any child type supported by make
  • returns the atom/0 type nil or the child type

Example

~Z"""
pub fn make_null_example() beam.term {
     return beam.make(null, .{});
}

pub fn make_optional_example(value: ?u32) beam.term {
     return beam.make(value, .{});
}
"""

test "make null" do
   assert is_nil(make_null_example())
end

test "make optional" do
   assert is_nil(make_optional_example(nil))
   assert 47 = make_optional_example(47)
end

arrays

  • supports arrays of any term that can be encoded using make
  • note that arrays have compile-time known length.
  • outputs as a list of the encoded terms
  • arrays of u8 default to outputting binary, this is the only exception to the above rule.
  • if the array's element is integers, floats, packed or extern structs, or arrays that support binaries, then the array can be output as binary data, by setting as option to .binary
  • if the array's element is u8 and you would prefer outputting as a list, setting as option to .list will do this.
  • to specify the internal encoding of the array, pass to as a struct with the field list; the value of the list field should be the encoding of the internal terms.

Examples

~Z"""
pub fn make_array_example() beam.term {
     const array = [_]u32{47, 48, 49};
     return beam.make(array, .{});
}

pub fn make_array_u8_example() beam.term {
     const array = "foo";
     return beam.make(array, .{});
}

pub fn make_array_binary_example() beam.term {
     const array = [_]u32{47, 48, 49};
     return beam.make(array, .{.as = .binary});
}

pub fn make_array_u8_list_example() beam.term {
     const array = "foo";
     return beam.make(array, .{.as = .list});
}

pub fn make_array_internal_encoding_example() beam.term {
     const aoa = [_][2]u32{[2]u32{47, 48}, [2]u32{49, 50}};
     return beam.make(aoa, .{.as = .{.list = .binary}});
}
"""

test "make u8 array" do
   assert "foo" = make_array_u8_example()
   assert [102, 111, 111] = make_array_u8_list_example()
end

test "make u32 array" do
   assert [47, 48, 49] = make_array_example()
   assert <<47::native-32, 48::native-32, 49::native-32>> = make_array_binary_example()
end

test "make array with internal encoding" do
   assert [
     <<47::native-32, 48::native-32>>,
     <<49::native-32, 50::native-32>>] = make_array_internal_encoding_example()
end

structs

  • supports structs with fields of any term that can be encoded using make
  • outputs as a map/0 with atom keys and the encoded terms as values
  • for packed or extern structs, supports binary data by setting as option to .binary. extern structs default to map encoding, packed structs default to binary encoding. All structs can be forced to map encoding by passing setting .as = .map.
  • encoding options can be specified by assigning as a struct with the field map; the value of the map field should be a struct with keys matching the struct fields and values being the encoding options for those fields. It's legal to omit a field from this specification, in which case the encoding will be .default.
  • supports anonymous structs
  • supports direct output as elixir structs using the as option

    Elixir structs

    • non-elixir alias-style structs are not currently supported, but will be in a future release
    • nested output formatting is not currently supported, but will be in a future release.
    • if you need to output a struct that is a multilevel alias, use @ syntax e.g. .{.as = .@"Foo.Bar.Baz"}

    the fields and types on the fields are not checked at compile time or runtime, so use with caution.

Examples

~Z"""
pub fn make_struct_example() beam.term {
   return beam.make(.{.foo = 123, .bar = "bar", .baz = .baz}, .{});
}

pub fn make_elixir_struct_example() beam.term {
   return beam.make(.{.first = 1, .last = 10, .step = 1}, .{.as = .Range});
}

const MakePacked = packed struct { value: u32 };

pub fn make_packed_struct() beam.term {
   return beam.make(MakePacked{.value = 47}, .{});
}

pub fn make_struct_nested() beam.term {
     const result = .{
         .list = [2]u32{47, 48},
         .binary = [2]u32{47, 48}
     };
     return beam.make(result, .{.as = .{.map = .{.binary = .binary}}});
}
"""

test "make general struct" do
   assert %{foo: 123, bar: "bar", baz: :baz} = make_struct_example()
end

test "make elixir struct" do
   assert 1..10 = make_elixir_struct_example()
end

test "make packed struct" do
   assert <<47::native-32>> = make_packed_struct()
end

test "make struct with nested format info" do
   assert %{
     binary: <<47::native-32, 48::native-32>>,
     list: [47, 48]
   } = make_struct_nested()
end

tuples

  • supports zig tuples with any term that can be encoded using make
  • outputs as a tuple/0.
  • encoding will always proceed using .default encoding. A scheme to specify encoding options is planned in the future.
  • note that the error atom should be encoded as .@"error"; you may also use beam.make_error_atom(...)

Examples

~Z"""
pub fn make_tuple_example() beam.term {
    return beam.make(.{.ok, "foo", 47}, .{});
}
"""
test "make tuple" do
   assert {:ok, "foo", 47} = make_tuple_example()
end

single-item-pointer

  • these pointers are only supported for arrays and structs
  • these are only supported because they are assumed to be pointers to mutable data
  • content will be dereferenced and encoded as if it were the child type
  • as rules apply (see arrays and structs).

Examples

~Z"""
pub fn make_pointer_example() beam.term {
     const array = [_]u32{47, 48, 49};
     return beam.make(&array, .{});
}
"""

test "make pointer" do
   assert [47, 48, 49] = make_pointer_example()
end

slice

  • supports arrays of any term that can be encoded using make
  • note that arrays have compile-time known length.
  • outputs as a list of the encoded terms
  • slices of u8 default to outputting binary, this is the only exception to the above rule.
  • if the slice's element is integers, floats, packed or extern structs, or arrays that support binaries, then the slice can be output as binary data, by setting as option to .binary
  • as rules (see arrays) apply.

Examples

~Z"""
pub fn make_slice_example(slice: []u32) beam.term {
    for (slice) |*item| { item.* += 1; }
    return beam.make(slice, .{});
}

pub fn make_slice_binary_example(slice: []u32) beam.term {
   return beam.make(slice, .{.as = .binary});
}
"""

test "make slice" do
   assert [48, 49, 50] = make_slice_example([47, 48, 49])
end

test "make slice as binary" do
   assert <<47::native-32, 48::native-32, 49::native-32>> = make_slice_binary_example([47, 48, 49])
end

many-item-pointer

  • only supports [:0]u8 and [:null]?Pointer.
  • as rules (see arrays) apply.

Examples

~Z"""
pub fn make_many_item_pointer_example(pointer: []u8) beam.term {
   pointer[5] = 0;
   const truncated: [*:0]u8 = @ptrCast(pointer.ptr);
   return beam.make(truncated, .{});
}
"""
test "make many item pointer" do
   assert "hello" = make_many_item_pointer_example("hello world")
end

cpointer

  • only supported if the pointer has child type u8 or pointer.
  • in the case of u8 interprets it as [*:0]u8.
  • in the case of Pointer interprets it as [*:null]?Pointer.
  • no other types are supported.
  • note that the content will be interpreted as the pointer type, so rules on pointers (see single-item-pointers))
  • as rules (see arrays) apply.

Examples

~Z"""
pub fn make_c_pointer_example(pointer: []u8) beam.term {
   pointer[5] = 0;
   const truncated: [*c]u8 = @ptrCast(pointer.ptr);
   return beam.make(truncated, .{});
}
"""

test "make c pointer" do
  assert "hello" = make_c_pointer_example("hello world")
end
Link to this nil

make_empty_list(anytype) term

View Source
@spec make_empty_list(anytype) :: term

returns the empty list term [].

This is a thin wrapper over e.enif_make_empty_list.

Example

~Z"""
pub fn make_empty_list_example() beam.term {
   return beam.make_empty_list(.{});
}
"""

test "make empty list" do
   assert [] = make_empty_list_example()
end
Link to this nil

make_error_atom(anytype) term

View Source
@spec make_error_atom(anytype) :: term

shortcut for make(.@"error", .{})

Example

~Z"""
pub fn make_error_atom_example() beam.term {
   return beam.make_error_atom(.{});
}
"""

test "make error atom" do
   assert :error = make_error_atom_example()
end
Link to this nil

make_error_pair(value: anytype, options: anytype) term

View Source
@spec make_error_pair(anytype, anytype) :: term

shortcut for make(env, .{.@"error", value}, options)

Link to this nil

make_into_atom(string: []const u8, anytype) term

View Source
@spec make_into_atom([]const u8, anytype) :: term

turns a []const u8 into a the corresponding atom/0 term.

returns a raised ArgumentError if the length of the string exceeds the vm atom size limit (255 bytes)

This is a thin wrapper over e.enif_make_atom_len.

Atom size limit

The BEAM VM has a limit of 255 bytes for atom size. This function does not protect against this and the API may change to accomodate this in the future.

Atom table exhaustion

The BEAM VM has a limit of 1_048_576 atoms. This function does not provide any protection against atom table exhaustion. A future version will protect against this case.

Example

~Z"""
pub fn make_into_atom_example() beam.term {
    return beam.make_into_atom("foo", .{});
}
"""

test "make into atom" do
  assert :foo = make_into_atom_example()
end
Link to this nil

make_list_cell(head: anytype, tail: term, opts: anytype) term

View Source
@spec make_list_cell(anytype, term, anytype) :: term

performs a list cons operation for head and tail variables

This is a thin wrapper over e.enif_make_list_cell.

Example

~Z"""
pub fn make_list_cell_example() beam.term {
    return beam.make_list_cell(47, beam.make_empty_list(.{}), .{});
}
"""

test "make list cell" do
   assert [47] = make_list_cell_example()
end
Link to this nil

make_ref(options: anytype) term

View Source
@spec make_ref(anytype) :: term

causes the VM to generate a new reference term equivalent to Kernel.make_ref/0

This is a thin wrapper over e.enif_make_ref.

Example

~Z"""
pub fn make_ref_example() beam.term {
     return beam.make_ref(.{});
}
"""

test "make_ref" do
   assert is_reference(make_ref_example())
end
Link to this nil

make_stacktrace(*std.builtin.StackTrace, options: anytype) term

View Source
@spec make_stacktrace(*std.builtin.StackTrace, anytype) :: term

converts a zig std.builtin.StackTrace into a special term that is designed to be translated and concatenated onto a BEAM stacktrace.

Example term:

[
   %{
     line_info: %{file_name: "/path/to/project/lib/my_app/.Elixir.MyApp.MyModule.zig", line: 15},
     symbol_name: "my_fun",
     compile_unit_name: "Elixir.MyApp.MyModule"
   }
   ...
]
Link to this nil

release_binary(*e.ErlNifBinary) void

View Source
@spec release_binary(*e.ErlNifBinary) :: void

marks a e.ErlNifBinary as qualified to be garbage collected. This is a thin wrapper over e.enif_release_binary.

Link to this nil

self(options: anytype) !pid

View Source
@spec self(anytype) :: !pid

returns a pid value that represents the current or parent process.

equivalent to Kernel.self/0

scope

This function succeeds in all contexts, except for callback contexts. For threaded processes, it will return the process that spawned the thread, whether or not that process is still alive.

Example:

~Z"""
pub fn self_example() !beam.pid {
   return try beam.self(.{});
}
"""

test "self" do
   assert self() == self_example()
end
Link to this nil

send(pid, data: anytype, options: anytype) anytype

View Source
@spec send(pid, anytype, anytype) :: anytype

sends data (as a term) to a target process' mailbox.

equivalent to Kernel.send/2

This function is a context-aware wrapper over e.enif_send. that also formats the message term using make

Note that send is designed so that you can switch concurrency modes without having to change your code.

Options

  • clear (boolean): whether the environment should be cleared after sending the message. Defaults to true. See information below.
  • persist (tuple of beam.term). Persists the terms into the new environment (see clear_env). It is not an error to pass persist in a process-bound context, though that will no-op.
  • as: any formatting options to be passed when making the term to be sent.

Return value

If there are no persisted values, send returns void. Otherwise it returns a tuple that corresponds to the tuple of persisted values, with new beam.term values.

send from raw nifs or non-process-bound threads

This function has undefined behaviour when called from raw nifs or calls that are not process-bound (e.g. if a new thread is started from a posix call that is not managed by zigler)

in the case of raw nifs, use e.enif_send directly instead.

in the case of non-process-bound threads or raw calls, if you use independent_context to initialize the environment, you can use send as normal.

The safety characteristics of raw and non-process-bound sending may be subject to change in future releases of Zigler.

clearing your environment

normally when calling e.enif_send you would need to call e.enif_clear_env to recycle the environment after sending. You do not need to do this for this function; it gets called automatically.

If you are certain that the send operation is the last operation in your function call, you may call the function as send(data, .{.clear = false}) and the clear_env function will not be called.

Example

~Z"""
pub fn send_example() !void {
     const self = try beam.self(.{});
     try beam.send(self, .{.foo, "bar", 47}, .{});
}
"""

test "send" do
   send_example()
   assert_receive {:foo, "bar", 47}
end
Link to this nil

term_to_binary(term, binary: anytype) !e.ErlNifBinary

View Source
@spec term_to_binary(term, anytype) :: !e.ErlNifBinary

converts a term/0 to a e.ErlNifBinary using erlang term format serialization.

This is a thin wrapper over e.enif_term_to_binary.

returns error.OutOfMemory if the allocation fails.

Functions

Link to this nil

make_general_purpose_allocator_instance() std.heap.GeneralPurposeAllocator(...)

View Source
@spec make_general_purpose_allocator_instance() :: std.heap.GeneralPurposeAllocator(...)

Types

@type Compared :: .lt | .gt | .eq

result type for compare

these atoms are used to conform to Elixir's Compare interface see: https://hexdocs.pm/elixir/1.13/Enum.html#sort/2-sorting-structs

@type Context :: {allocator :: mem.Allocator, env :: env, mode :: beam.ContextMode}

a datastructure containing information that is local to the current call. managed entrypoints into any given nif (any nif that isn't raw) will set have access to the threadlocal context/0 variable.

Fields

  • mode: the concurrency mode of the nif
  • env: the environment of the nif
  • allocator: the allocator to use for allocations

fields

  • mode: ContextMode

  • env: env

  • allocator: std.mem.Allocator

@type ContextMode ::
  .independent | .dirty_yield | .yielding | .threaded | .synchronous | .callback | .dirty

a tag identifying the type of context in which the nif is running

See nif documentation for more detailed information about the concurrency strategies.

  • .synchronous: the execution context of a synchronous nif
  • .threaded: the execution context of a nif that runs in its own os thread
  • .dirty: the execution context of a nif that runs on a dirty scheduler
  • .yielding: the execution context of a nif that runs cooperatively with the BEAM scheduler
  • .callback: the execution context of module setup/teardown callbacks or a resource destruction callback
  • .dirty_yield: the execution context of a dirty nif whose parent process has been terminated but the nif is still running.
  • .independent: the execution context of functions that are not associated with nifs. see independent_context for how to set this as the context.

See context for the threadlocal variable that stores this.

raw beam functions

nifs called in raw mode are not assigned an execution context.

@type TermType ::
  .ref | .bitstring | .tuple | .fun | .map | .float | .port | .pid | .list | .integer | .atom

A zig enum equivalent to e.ErlNifTermType

retrievable from term using the beam.term_type method.

@type env :: env

identical to ?*e.ErlNifEnv

env

env should be considered an opaque type that can be passed around without inspection of its contents.

@type event :: erl_nif_event

identical to e.ErlNifEvent. This is an event datatype that the BEAM documentation does not describe.

@type loader :: {}

boilerplate functions for nif module initialization. Contains:

  • blank_load
  • blank_upgrade
  • blank_unload

which are no-op versions of these functions.

@type monitor :: e.ErlNifMonitor

identical to e.ErlNifMonitor. This is a monitor datatype that the BEAM documentation does not describe.

@type pid :: pid

identical to ?*e.ErlNifPid

@type port :: e.ErlNifPort

identical to ?*e.ErlNifPort

@type term :: term

wrapped term.

e.ErlNifTerm is, under the hood, an integer type. This is wrapped in a singleton struct so that that semantic analysis can identify and distinguish between a 'plain' integer and a term.

The Zig compiler will optimize this away, so there is no runtime cost to passing this around versus e.ErlNifTerm, and the following conversion operations are no-ops:

  • To convert to a raw e.ErlNifTerm, access the .v field.

  • To convert a raw e.ErlNifTerm to this term, use an anonymous struct:

       .{.v = erl_nif_term_value}

term_type

the struct function term_type returns the TermType of the internal term.

const t = beam.term.make(env, 47, .{});
const term_type = t.term_type(env); // -> .integer
@type tid :: e.ErlNifTid

identical to e.ErlNifTid. This is a thread id datatype that the BEAM. documentation does not describe.

Constants

Link to this nil

allocator: mem.Allocator

View Source
@spec allocator :: mem.Allocator

wraps e.enif_alloc and e.enif_free into the zig standard library allocator interface.

Link to this nil

general_purpose_allocator: mem.Allocator

View Source
@spec general_purpose_allocator :: mem.Allocator

implements std.mem.Allocator using the std.mem.GeneralPurposeAllocator factory, backed by beam.wide_alignment_allocator.

Link to this nil

wide_alignment_allocator: mem.Allocator

View Source
@spec wide_alignment_allocator :: mem.Allocator

provides a BEAM allocator that can perform allocations with greater alignment than the machine word.

Memory performance

This comes at the cost of some memory to store metadata

currently does not release memory that is resized. For this behaviour use beam.general_purpose_allocator.

not threadsafe. for a threadsafe allocator, use beam.general_purpose_allocator

Variables

Link to this nil

context: beam.Context

View Source (threadlocal)
@spec context :: beam.Context

threadlocal variable that stores the execution context for the nif. Execution of any managed (non-raw) nif is guaranteed to have access to this variable, and the value of this variable will not be changed across any reentries into the function (for example, for yielding nifs)

If you spawn a thread from your nif, you should copy anything you need out of this variable.

See Context for the list of fields.

context starts undefined

This threadlocal is set to undefined because of architectural differences: we cannot trust loaded dynamic libraries to properly set this on thread creation.

raw function calls do not set context