beam (zigler v0.15.2)
View SourceThis 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
a function which returns a new debug allocator instance.
Constants
provides a BEAM allocator that can perform allocations with greater alignment than the machine word.
implements std.mem.Allocator using the std.mem.DebugAllocator
factory, backed by beam.wide_alignment_allocator.
directly wraps the allocator functions into the std.mem.Allocator
interface. This will only allocate memory at the machine word alignment.
if you need greater alignment, use beam.allocator
Functions
@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
@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")
endbinary 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.
@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
@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 threadlocalbeam.context.allocatorvaluesize: if the value does not have a defined size, for example aManypointer, 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_debug_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
@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
@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
@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.
@spec get(T, beam.term, anytype) :: T
converts BEAM term dynamic types into static zig types
The arguments are as follows:
- destination type
- term to convert
- 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>>
)
endenum
- 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)
endfloat
- supports
f16,f32, andf64types. - may be passed a BEAM
float/0term - atoms
:infinity,:neg_infinity,:NaNare 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/0withatom/0keys and values of the appropriate type - may be passed a
keyword/0list withatom/0keys and values of the appropriate type. - inner values are recursively marshalled to to the appropriate type.
- if the struct is
packedorextern, 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>>)
endpacked 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
trueandfalseboolean/0terms 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)
endarray
- 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>>)
endsingle-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>>)
endslice
- 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>>)
endmany-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
*usizeparameter as thesizeparameter 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>>)
endcpointer
allocates memory based on allocator provided in the options, otherwise defaults to
beam.allocatorsupports 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
*usizeparameter as thesizeparameter 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>>)
endoptional
- accepts
atom/0nilas 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)
endpid
- accepts
pid/0
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
endSupported options
env: required forrawnifs. 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 atermthat 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): iftrue(default) the refcount will be increased on the term as a result of performing thegetoperation.size(* usize, only applies to manypointer or[*c]):
@spec get_env() :: env
convenience function to get the context's execution environment
@spec get_list_cell(term, anytype) ::
!struct({beam.term__struct_23171, beam.term__struct_23171})
This is a thin wrapper over e.enif_get_list_cell.
See also make_list_cell for the reverse operation.
Example
~Z"""
pub fn get_list_cell_example(term: beam.term) !i32 {
const x, const y = try beam.get_list_cell(term, .{});
return try beam.get(i32, x, .{}) + try beam.get(i32, y, .{});
}
"""
test "get list_cell " do
assert 47 = get_list_cell_example([40 | 7])
end
@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
endfree independent context envs
you must ALWAYS free beam.context.env if you have created an independent
context.
@spec make(anytype, anytype) :: term
converts static zig types into BEAM term dynamic types
The arguments are as follows:
- environment
valueto convert to termoptionsstruct (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)
endvoid
- 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()
endpid
~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())
endstd.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/0term - 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()
endfloats
- supports
f16,f32, andf64types. - supports comptime float type.
- returns a BEAM
float/0term - may also return one of the
atom/0type: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()
endbool
- supports
booltypes. - returns a BEAM
boolean/0term
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)
endenum or error enum
- supports
enumorerrortypes. - doesn't support zero or one-item enums.
- returns a BEAM
atom/0term - 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()
endEnum 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
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)
endarrays
- 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
u8default 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
asoption to.binary - if the array's element is u8 and you would prefer outputting as a list,
setting
asoption to.listwill do this. - to specify the internal encoding of the array, pass to
asa struct with the fieldlist; 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()
endstructs
- supports structs with fields of any term that can be encoded using
make - outputs as a
map/0with atom keys and the encoded terms as values - for
packedorexternstructs, supports binary data by settingasoption to.binary.externstructs default to map encoding,packedstructs default to binary encoding. All structs can be forced tomapencoding by passing setting.as = .map. - encoding options can be specified by assigning
asa struct with the fieldmap; 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
structoption
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}, .{.@"struct" = .@"Elixir.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()
endtuples
- supports zig tuples with any term that can be encoded using
make - outputs as a
tuple/0. - encoding will always proceed using
.defaultencoding. A scheme to specify encoding options is planned in the future. - note that the error atom should be encoded as
.@"error"; you may also usebeam.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()
endsingle-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
asrules 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()
endslice
- 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
u8default 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
asoption to.binary asrules (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])
endmany-item-pointer
- only supports [:0]u8 and [:null]?Pointer.
asrules (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")
endcpointer
- only supported if the pointer has child type
u8or pointer. - in the case of
u8interprets it as[*:0]u8. - in the case of
Pointerinterprets 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))
asrules (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
@spec make_debug_allocator_instance() :: std.heap.GeneralPurposeAllocator(...)
a function which returns a new debug allocator instance.
@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
@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
@spec make_error_pair(anytype, anytype) :: term
shortcut for make(env, .{.@"error", value}, options)
@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
@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
@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
@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:
[
%{
source_location: %{
file_name: "/path/to/project/lib/my_app/.Elixir.MyApp.MyModule.zig",
line: 15,
column: 5
},
symbol_name: "my_fun",
compile_unit_name: "Elixir.MyApp.MyModule"
}
...
]
@spec raise_elixir_exception([]const u8, anytype, anytype) :: term
The equivalent of Kernel.raise/1 from elixir.
moduleshould be the name of the module that represents the exceptiondatashould 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
endException structs are not type checked
Unlike code in elixir, the validity of the exception struct is not checked when using this function.
@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
@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.
@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.
@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
@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 totrue. See information below.persist(tuple ofbeam.term). Persists the terms into the new environment (seeclear_env). It is not an error to passpersistin 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
@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.
@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.Thread.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
Types
Constants
@spec 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 and some performance on the allocation step.
@spec debug_allocator :: mem.Allocator
implements std.mem.Allocator using the std.mem.DebugAllocator
factory, backed by beam.wide_alignment_allocator.
@spec raw_allocator :: mem.Allocator
directly wraps the allocator functions into the std.mem.Allocator
interface. This will only allocate memory at the machine word alignment.
if you need greater alignment, use beam.allocator
Variables
@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