### C FFI example: plus_one function Source: https://github.com/e-xyza/zigler/blob/main/guides/06-c_integration.md Defines C function `plus_one`, shows how to include it and call from Elixir. ```C // forwarded function definition int plus_one(int); ``` ```C int plus_one(int value) { return value + 1; } ``` -------------------------------- ### Link Against C ABI Library Source: https://github.com/e-xyza/zigler/blob/main/guides/06-c_integration.md Shows how to link against a system library (e.g., `.a`, `.so`, `.obj`, or `.dll`) using Zigler. The example demonstrates how to use the `cblas_dasum` function from the `blas` library and provides instructions for distributing the library file within the project. ```Elixir if Application.fetch_env!(:zigler, :test_blas) do defmodule LibraryTest do use ExUnit.Case, async: true use Zig, otp_app: :zigler, c: [link_lib: {:system, "blas"}] ~Z"" pub const dasum = @cImport(@cInclude("cblas.h")).cblas_dasum; "" test "dasum" do assert 6.0 == dasum(3, [1.0, 2.0, 3.0], 1) end end end ``` -------------------------------- ### Installing Zigler in Erlang via rebar3 Source: https://github.com/e-xyza/zigler/blob/main/README.md For experimental Erlang support, enable the rebar_mix plugin and add Zigler to deps in rebar.config. Zig installation path is ~/.cache/zigler/zig-linux--0.15.1; full setup is TBD and may require issue reporting for issues. ```erlang {plugins, [rebar_mix]}. {deps, [{zigler, "0.14"}]}. ``` -------------------------------- ### Installing Zigler in Elixir via mix.exs Source: https://github.com/e-xyza/zigler/blob/main/README.md Add Zigler to the dependencies list in mix.exs for Elixir projects. Requires running mix deps.get followed by mix zig.get to obtain the Zig compiler, or set ZIG_ARCHIVE_PATH for a local installation. Runtime is false as it's a compile-time dependency. ```elixir def deps do [ {:zigler, "~> 0.15.1", runtime: false} ] end ``` -------------------------------- ### Easy C - Direct C Function Binding in Elixir Source: https://context7.com/e-xyza/zigler/llms.txt Automatically generates Elixir NIFs directly from C header files, eliminating the need for manual Zig wrapper code. This example binds the `cblas_daxpy` function from the 'cblas.h' header, configuring its parameters and return type for efficient Elixir integration. ```elixir defmodule EasyCBlas do use Zig, otp_app: :my_app, easy_c: "cblas.h", c: [link_lib: {:system, "blas"}], nifs: [ cblas_daxpy: [ params: %{4 => :in_out}, return: [length: {:arg, 0}] ] ] # No ~Z code needed - function is automatically bound end # Usage - cblas_daxpy computes y = a*x + y EasyCBlas.cblas_daxpy( 3, # length 2.0, # a [1.0, 2.0, 3.0], # x 1, # x stride [4.0, 5.0, 6.0], # y (in-out parameter) 1 # y stride ) # => [6.0, 9.0, 12.0] ``` -------------------------------- ### Configuring Zigler Formatter in Elixir Source: https://github.com/e-xyza/zigler/blob/main/README.md Extend .formatter.exs to include Zig files in formatting inputs and add Zig.Formatter plugin. Applies to lib, test, config, and installer directories with .ex, .exs, .zig extensions. Ensures consistent code style across Elixir and Zig. ```elixir [ inputs: ~w[ {mix,.formatter,.credo}.exs {config,lib,rel,test}/**/*.{ex,exs,zig} installer/**/*.{ex,exs} ], plugins: [Zig.Formatter] ] ``` -------------------------------- ### Using Zig Allocators in beam.get for Resource Management Source: https://github.com/e-xyza/zigler/blob/main/guides/03-allocators.md Illustrates how to utilize Zig's standard library allocators, specifically `std.heap.c_allocator` (malloc), within the `beam.get` function to instantiate data. The example shows allocating a slice of u64, summing its elements, and ensuring the allocated memory is freed by the allocator. ```zig pub fn arena_sum(array: beam.term) !u64 { // use the zig std-provided heap allocator. const malloc = @import("std").heap.c_allocator; const slice = try beam.get([]u64, array, .{.allocator = malloc}); defer malloc.free(slice); var total: u64 = 0; for (slice) |item| { total += item; } return total; } ``` -------------------------------- ### Documenting Zig NIF Functions in Elixir Source: https://github.com/e-xyza/zigler/blob/main/README.md Add documentation comments in Zig code using /// for NIF functions, which integrate into Elixir module docs accessible via IEx h/. Supports documenting structs, variables, and types. Example shows a zero-arity function returning i64 47. ```elixir defmodule Documentation do use Zig, otp_app: :zigler ~Z""" /// a zero-arity function which returns 47. pub fn zero_arity() i64 { return 47; } """ end ``` ```zig /// a zero-arity function which returns 47. pub fn zero_arity() i64 { return 47; } ``` -------------------------------- ### Compiling Custom C/C++ Source in Elixir Source: https://context7.com/e-xyza/zigler/llms.txt Compiles custom C or C++ source files using Zig's bundled toolchain and integrates them into an Elixir module. This example compiles a C file that defines a 'square' function and exposes it to Elixir, along with a Zig function that uses it. ```elixir # File: include/math_ops.h # int square(int x); # File: src/math_ops.c # int square(int x) { return x * x; } defmodule CustomCCode do use Zig, otp_app: :my_app, c: [ include_dirs: "include", src: ["src/math_ops.c"] ] ~Z""" const c = @cImport(@cInclude("math_ops.h")); pub const square = c.square; pub fn sum_of_squares(a: i32, b: i32) i32 { return c.square(a) + c.square(b); } """ end # Usage CustomCCode.square(7) # => 49 CustomCCode.sum_of_squares(3, 4) # => 25 ``` -------------------------------- ### Handle Optional Values Between Elixir and Zig Source: https://github.com/e-xyza/zigler/blob/main/guides/01-nifs.md Illustrates the use of optional values as both input and output between Elixir and Zig. It highlights that `nil` in Elixir corresponds to `null` in Zig. The example function `optional` takes an optional unsigned 32-bit integer and returns an optional unsigned 32-bit integer, demonstrating the conversion of empty optionals. ```elixir ~Z""" pub fn optional(number: ?u32) ?u32 { if (number) | n | { if (n == 42) return null; return n; } else { return 47; } } """ test "optionals" do assert is_nil(optional(42)) assert 47 = optional(nil) assert 10 = optional(10) end ``` -------------------------------- ### Raw NIF with beam.term signature Source: https://github.com/e-xyza/zigler/blob/main/guides/09-raw_nifs.md This snippet shows a raw NIF function using the beam.term signature. It demonstrates how to handle inputs and return a beam.term. The function must include the arity option in the nif setup. ```zig pub fn raw_call_beam(env: beam.env, count: c_int, list: [*]const beam.term) beam.term { return beam.make(.{.count = count, .item = list[0]}, .{.env = env}); } ``` -------------------------------- ### Utilize BEAM Allocator for Memory Management in Zig NIFs Source: https://context7.com/e-xyza/zigler/llms.txt Demonstrates how to use the integrated BEAM allocator within Zig NIFs for memory-safe allocations tracked by the Elixir VM. This example shows duplicating strings and allocating a sequence of unsigned 64-bit integers, ensuring memory is managed by the BEAM's garbage collector. ```elixir defmodule Allocations do use Zig, otp_app: :my_app ~Z""" const beam = @import("beam"); pub fn duplicate_string(str: []u8) !beam.term { var doubled = try beam.allocator.alloc(u8, str.len * 2); defer beam.allocator.free(doubled); for (str, 0..) |char, i| { doubled[i] = char; doubled[i + str.len] = char; } return beam.make(doubled, .{}); } pub fn allocate_sequence(count: usize) ![]u64 { const slice = try beam.allocator.alloc(u64, count); for (slice, 0..) |*entry, index| { entry.* = index; } return slice; } """ end # Usage Allocations.duplicate_string("abc") # => "abcabc" Allocations.allocate_sequence(5) # => [0, 1, 2, 3, 4] ``` -------------------------------- ### Dirty CPU NIF Implementation in Zigler (Elixir & Zig) Source: https://github.com/e-xyza/zigler/blob/main/guides/07-concurrency.md This example demonstrates implementing a long-running NIF using the dirty_cpu option in Zigler to avoid blocking the main VM thread. It imports the beam module to yield control and handle process termination safely. The function takes a PID as input, sends messages to indicate status, and runs an infinite loop yielding periodically; limitation is potential blocking if all dirty CPU schedulers are consumed. ```elixir defmodule DirtyCpu do use ExUnit.Case, async: true use Zig, otp_app: :zigler, nifs: [long_running: [:dirty_cpu]] ~Z""" const beam = @import("beam"); // this is a dirty_cpu nif. pub fn long_running(pid: beam.pid) !void { defer { // code in the defer block is triggered when process is killed. // we need to create a new thread-independent context because // the context from the running process is now invalid. const env = beam.alloc_env(); beam.send(pid, .killed, .{.env = env}) catch unreachable; beam.free_env(env); } try beam.send(pid, .unblock, .{}); while(true) { try beam.yield(); } } """ test "dirty cpu can be cancelled" do this = self() dirty_cpu = spawn(fn -> long_running(this) end) assert_receive :unblock Process.exit(dirty_cpu, :kill) assert_receive :killed end end ``` -------------------------------- ### Raw NIF with e.ErlNifTerm signature Source: https://github.com/e-xyza/zigler/blob/main/guides/09-raw_nifs.md This snippet shows a raw NIF function using the e.ErlNifTerm signature. It demonstrates how to handle inputs and return an e.ErlNifTerm. The function must include the arity option in the nif setup. ```zig pub fn raw_call_erl_nif(env: beam.env, count: c_int, list: [*]const e.ErlNifTerm) e.ErlNifTerm { return beam.make(.{.count = count, .item = beam.term{.v = list[0]}}, .{.env = env}).v; } ``` -------------------------------- ### Handle Errors Between Elixir and Zig using ErlangException Source: https://github.com/e-xyza/zigler/blob/main/guides/01-nifs.md Shows how functions returning errors in Zig can be caught in Elixir as `ErlangException`. The example defines a custom error `OopsError` in Zig and a function `erroring` that returns this error. Elixir's `assert_raise` is used to verify that calling `erroring` throws the expected `ErlangError`. ```elixir ~Z""" const OopsError = error { oops }; pub fn erroring() !void { return error.oops; } """ @tag :erroring test "erroring" do assert_raise ErlangError, "Erlang error: :oops", fn -> erroring() end end ``` -------------------------------- ### Threaded Mode NIF Implementation in Zigler (Elixir & Zig) Source: https://github.com/e-xyza/zigler/blob/main/guides/07-concurrency.md This example shows how to run a NIF in a separate thread using the threaded option in Zigler, allowing it to run without affecting the VM's primary threads. It uses beam.yield to periodically yield and detect process termination. The function accepts a PID, sends unblock and killed messages, and loops indefinitely; note that the env is not process-bound, and yielding returns must be quick (within 750us) to prevent leaks. ```elixir defmodule Threaded do use ExUnit.Case, async: true use Zig, otp_app: :zigler, nifs: [long_running: [:threaded]] ~Z""" const beam = @import("beam"); const std = @import("std"); pub fn long_running(pid: beam.pid) !void { // following code triggered when process is killed. defer { beam.send(pid, .killed, .{}) catch {}; } while(true) { _ = try beam.send(pid, .unblock, .{}); try beam.yield(); } } """ @tag :threaded test "threaded can be cancelled" do this = self() threaded = spawn(fn -> long_running(this) end) #assert_receive :unblock Process.sleep(100) Process.exit(threaded, :kill) assert_receive :killed Process.sleep(1000) end end ``` -------------------------------- ### Compile C/C++ with Zigler Source: https://github.com/e-xyza/zigler/blob/main/guides/06-c_integration.md Demonstrates how to compile C/C++ files using Zigler's toolchain. It involves specifying include directories and source paths using module options like `include_dirs` and `src`. The `src` option can also use wildcard to include all c/cpp files from a directory. ```Elixir defmodule CompilingC do use ExUnit.Case, async: true use Zig, otp_app: :zigler, c: [include_dirs: "include", src: "src/*"] ~Z"" const c = @cImport(@cInclude("included.h")); pub const plus_one = c.plus_one; "" test "c plus one" do assert 48 = plus_one(47) end end ``` -------------------------------- ### Elixir: Using Precompiled Module (Web with Template) Source: https://github.com/e-xyza/zigler/blob/main/guides/11-precompiled.md Demonstrates using a template address with platform-specific hashes for web assets. ```elixir @lib_address "https://address-for-archived-libraries/MyModuleName.#VERSION.#TRIPLE.#EXT" @shasum [ "aarch64-freebsd-none": "e7b413de8bbf37876773dac05f3410f0bf3cc80f99d897557b6819e8189fb006", "aarch64-linux-gnu": "17546c34adf8b6a14dd38ebb9d5485610348e5afff72e88b991f18d6b818197f", "aarch64-linux-musl": "4ccae1cb87fbef01a556b7e8834da256a5dfa6f8a68d57d95005680ce4e37e16", "aarch64-macos-none": "5006c503b8b5d4efff875a2979f6aab7e1cc3c0360fa8618343157964aec1d15", "arm-linux-gnueabi": "4e56cd1447ba3b565756d7298a6d89e8cbff13966a27c56fce5a72ad23a9ea4d", "arm-linux-gnueabihf": "2355e863c21c1120de0776c98b3ab268e6d633ee3e48d47a6477c9a4a4efee58", "arm-linux-musleabi": "5de29e68005eddf6fa31d6f10532de094071fef89f9ffc3eb701e0d0ea550ff3", "arm-linux-musleabihf": "28bc7c872d21eb8524bb2c35b502f4f42f10f0f05c2bc4420b45142e51224b35", "x86_64-freebsd-none": "a7ff7317461d0a43ffac60c131320c3e5e005f0e75f9562bb710718c476c85fc", "x86_64-linux-gnu": "e38e1dfdd85d711f85e0ecbb42fc34e102cd9dc37079c200e2075cb894acad7c", "x86_64-linux-musl": "857b0ed35c78b588fa43dc4a5c4e3dd9143d9102c559a67812b0af6185eae117", "x86_64-macos-none": "e5ff347c2d11b463b5e6f0097687d578c800e7fbb725aa33a2e802d3f473371c", "x86_64-windows-gnu": "3ee18aec252eca92f19d920f6de143e59700e0f038916fb3717aa9869b2c5102", "x86-linux-gnu": "9ef68f25143827e6cbea13fa264f0002d7d1f2fc2ad7c07febe5be5fbc4f75c6", "x86-linux-musl": "227afab8c4a20b4dc88ae0efeb0f6a93952c9ad54aa3165d166cebd66ae5d158", "x86-windows-gnu": "4602d68145becb66d4f357ab163a5cb8a7f5466a4004cd6419f998c241b52d2b" ] use Zig, otp_app: :zigler, precompiled: {:web, @lib_address, @shasum}, ... ``` -------------------------------- ### Elixir: Using Precompiled Module (Local) Source: https://github.com/e-xyza/zigler/blob/main/guides/11-precompiled.md Demonstrates how to use a locally precompiled NIF library. Requires the `:precompiled` option with a local file path. ```elixir use Zig, otp_app: :zigler, precompiled: "./priv/lib/precompiled.so" ``` -------------------------------- ### Elixir: Using Precompiled Module (Web) Source: https://github.com/e-xyza/zigler/blob/main/guides/11-precompiled.md Shows how to load a precompiled NIF from a web address, including specifying a shasum for integrity verification. ```elixir use Zig, otp_app: :zigler, precompiled: {:web, "https://address-for-archive/precompiled-artifact.so", "935f9829d4c0058acba4118c9dc1a98dbdda5c4035e16a7893c33a3aff2caee8"}, ... ``` -------------------------------- ### Raw NIF with multiple arities Source: https://github.com/e-xyza/zigler/blob/main/guides/09-raw_nifs.md This snippet shows a raw NIF function that supports multiple arities. It demonstrates how to handle different input counts and return a beam.term. The function must include the arity option in the nif setup. ```zig pub fn raw_call_multi_arity(env: beam.env, arity: c_int, _: [*]const beam.term) beam.term { return beam.make(arity, .{.env = env}); } ``` -------------------------------- ### Configure Elixir module with Zigler NIF support Source: https://github.com/e-xyza/zigler/blob/main/guides/01-nifs.md Activates Zigler in an Elixir module using the `use Zig` directive with the required `otp_app` option. This enables Zig code compilation and sets up the directory for compilation artifacts. Also integrates ExUnit for testing. ```elixir defmodule NifGuideTest do use Zig, otp_app: :zigler use ExUnit.Case, async: true ``` -------------------------------- ### Error Handling with Zig Error Types Source: https://context7.com/e-xyza/zigler/llms.txt Handle errors gracefully by converting Zig error unions to Elixir error tuples. This allows for idiomatic error propagation and handling in Elixir code. The example demonstrates handling division by zero, negative input, and overflow errors. ```elixir defmodule ErrorHandling do use Zig, otp_app: :my_app ~Z""" const MyError = error{ DivisionByZero, NegativeInput, Overflow }; pub fn safe_divide(a: f64, b: f64) !f64 { if (b == 0.0) return error.DivisionByZero; return a / b; } pub fn factorial(n: i64) !i64 { if (n < 0) return error.NegativeInput; if (n > 20) return error.Overflow; if (n <= 1) return 1; return n * try factorial(n - 1); } """ end # Usage ErrorHandling.safe_divide(10.0, 2.0) # => 5.0 ErrorHandling.safe_divide(10.0, 0.0) # Raises ArgumentError ErrorHandling.factorial(5) # => 120 ErrorHandling.factorial(-1) # Raises ArgumentError ErrorHandling.factorial(25) # Raises ArgumentError ``` -------------------------------- ### Define and Sum Array-Like Structs in Zig Source: https://github.com/e-xyza/zigler/blob/main/guides/02-collections.md Defines a 'PointSOA' struct with array-like fields for 'x' and 'y' coordinates and a function 'sum_point_soa' to calculate the sum of these coordinates. This example showcases basic struct definition and iteration in Zig. It takes a 'PointSOA' struct as input and returns a 'Point2D' struct. ```zig pub const PointSOA = struct{ x: []u16, y: []u16 }; pub fn sum_point_soa(points: PointSOA) Point2D { var result: Point2D = .{.x = 0, .y = 0}; for (points.x) |x| { result.x += x; } for (points.y) |y| { result.y += y; } return result; } ``` -------------------------------- ### Define C Function NIFs with Easy-C and Zigler Source: https://github.com/e-xyza/zigler/blob/main/guides/06-c_integration.md This Elixir code defines a test module using Zigler to create NIFs from a C header file ('cblas.h'). It demonstrates how to link a system library ('blas') and hoist C functions like `cblas_daxpy`, specifying 'in-out' parameters and return types such as binaries with a dynamic length based on an argument. ```elixir defmodule EasyCTest do use ExUnit.Case, async: true use Zig, otp_app: :zigler, easy_c: "cblas.h", c: [link_lib: {:system, "blas"}], nifs: [ cblas_daxpy: [params: %{4 => :in_out}, return: [length: {:arg, 0}]], cblas_daxpy_bin: [alias: :cblas_daxpy, params: %{4 => :in_out}, return: [:binary, length: {:arg, 0}]] ] test "daxpy as a list" do assert [7.0, 11.0, 15.0] == cblas_daxpy(3, 3.0, [1.0, 2.0, 3.0], 1, [4.0, 5.0, 6.0], 1) end test "daxpy as a binary" do assert <<7.0::float-native, 11.0::float-native, 15.0::float-native>> == cblas_daxpy_bin(3, 3.0, [1.0, 2.0, 3.0], 1, [4.0, 5.0, 6.0], 1) end end ``` -------------------------------- ### Map Zig Enums to Elixir Atoms for Type Safety Source: https://context7.com/e-xyza/zigler/llms.txt Explains how to map Zig enumerations to Elixir atoms, enhancing type safety in API design. This example defines Zig enums for status and direction and implements functions to manipulate them, showing direct conversion between Zig enum values and Elixir atoms. ```elixir defmodule EnumExample do use Zig, otp_app: :my_app ~Z""" const Status = enum { ok, error, pending }; pub fn flip_status(input: Status) Status { return switch (input) { .ok => .error, .error => .ok, .pending => .pending, }; } const Direction = enum(u8) { north = 0, south = 1, east = 2, west = 3 }; pub fn opposite_direction(dir: Direction) Direction { return switch (dir) { .north => .south, .south => .north, .east => .west, .west => .east, }; } """ end # Usage EnumExample.flip_status(:ok) # => :error EnumExample.opposite_direction(:north) # => :south ``` -------------------------------- ### Integrate External Zig Files as Elixir NIFs Source: https://context7.com/e-xyza/zigler/llms.txt Shows how to load Zig code from an external `.zig` file into an Elixir module, allowing for better code organization. This requires specifying the `zig_code_path` option when using the `Zig` macro. The external Zig file must contain the desired functions. ```elixir # File: my_math.zig # pub fn factorial(n: u64) u64 { # if (n <= 1) return 1; # return n * factorial(n - 1); # } defmodule ExternalZig do use Zig, otp_app: :my_app, zig_code_path: "my_math.zig" end # Usage ExternalZig.factorial(5) # => 120 ``` -------------------------------- ### Configure Zig Release Mode with optimize Option in Zigler Source: https://github.com/e-xyza/zigler/blob/main/guides/08-module_options.md Explains how to control the Zig compiler's optimization level using the `optimize` option in `use Zig`. Options include `:debug`, `:safe`, `:fast`, and environment variable-based modes like `:env` or `{:env, default}`. ```elixir defmodule ReleaseMode do use ExUnit.Case, async: true use Zig, otp_app: :zigler, optimize: :fast ~Z""" const beam = @import("beam"); pub fn get_mode() beam.term { return beam.make(@import("builtin").mode, .{}); } """ test "release mode" do assert :ReleaseFast == get_mode() end end ``` -------------------------------- ### Define Basic NIFs with Inline Zig Code in Elixir Source: https://context7.com/e-xyza/zigler/llms.txt Demonstrates how to define and use basic Zig functions as NIFs directly within an Elixir module using the `~Z` sigil. This method handles compilation and integration automatically. It requires the `Zig` dependency and an `otp_app` configuration. ```elixir defmodule BasicExample do use Zig, otp_app: :my_app ~Z""" pub fn add_one(number: i64) i64 { return number + 1; } pub fn multiply(a: f64, b: f64) f64 { return a * b; } """ end # Usage BasicExample.add_one(47) # => 48 BasicExample.multiply(3.5, 2.0) # => 7.0 ``` -------------------------------- ### Define on_load callback in Zigler module Source: https://github.com/e-xyza/zigler/blob/main/guides/10-callbacks.md Sets up an on_load callback function that initializes module private data when the module loads. The callback supports multiple signatures including error handling variants. Requires a corresponding __on_load__/0 function in the Elixir module to provide initialization data. ```elixir defmodule OnLoadExample do use ExUnit.Case, async: true use Zig, otp_app: :zigler, callbacks: [on_load: :load_fn] defp __on_load__, do: 47 ~Z""" const beam = @import("beam"); const e = @import("erl_nif"); pub fn load_fn(private: ?*?*u32, number: u32) !void { const stored_pointer = try beam.allocator.create(u32); stored_pointer.* = number; private.?.* = stored_pointer; } pub fn get_private() u32 { const priv_ptr: ?*anyopaque = e.enif_priv_data(beam.context.env); const priv_ptr_u32: *u32 = @ptrCast(@alignCast(priv_ptr.?)); return priv_ptr_u32.*; } """ test "on_load stores value into private" do assert 47 = get_private() end end ``` -------------------------------- ### Composable Arena Allocator with Zig's Beam Allocator Source: https://github.com/e-xyza/zigler/blob/main/guides/03-allocators.md Demonstrates creating a composable arena allocator using Zig's standard library ArenaAllocator, integrated with Zigler's beam.allocator. It shows allocating memory, populating it, and returning it as a beam.term. The arena is deinitialized upon function exit. ```zig pub fn with_arena() !beam.term { var arena = std.heap.ArenaAllocator.init(beam.allocator); defer arena.deinit(); const allocator = arena.allocator(); const slice = try allocator.alloc(u16, 4); defer allocator.free(slice); for (slice, 0..) |*item, index| { item.* = @intCast(index); } return beam.make(slice, .{}); } ``` -------------------------------- ### Send Values Between Elixir and Zig using beam.send Source: https://github.com/e-xyza/zigler/blob/main/guides/01-nifs.md Demonstrates exporting values from a Zig NIF to an Elixir process using the `beam.send` function. It shows how to send a tuple `{:ok, 47}` to a given process ID. Dependencies include the `beam` module. The function takes a `beam.pid` and returns `void` on success. ```elixir ~Z""" pub fn test_send(pid: beam.pid) !void { try beam.send(pid, .{.ok, 47}, .{}); } """ test "sending" do test_send(self()) assert_receive {:ok, 47} end ``` -------------------------------- ### Marshal Collections (Lists, Slices, Strings) Between Elixir and Zig Source: https://context7.com/e-xyza/zigler/llms.txt Illustrates automatic type conversion for collections like Elixir lists and binaries to Zig slices and arrays, and vice versa. This snippet covers string length calculation, summing lists of floats, and creating Zig arrays. It leverages the `~Z` sigil for inline Zig code. ```elixir defmodule Collections do use Zig, otp_app: :my_app ~Z""" pub fn string_length(str: []u8) i64 { return @intCast(str.len); } pub fn list_sum(numbers: []f64) f64 { var sum: f64 = 0.0; for (numbers) |num| { sum += num; } return sum; } pub fn create_array() [3]u8 { return .{97, 98, 99}; } """ end # Usage Collections.string_length("hello") # => 5 Collections.list_sum([1.0, 2.0, 3.0, 4.0]) # => 10.0 Collections.create_array() # => "abc" (binary output for u8) ``` -------------------------------- ### Manual Type Marshalling with beam.get and beam.make in Elixir Source: https://context7.com/e-xyza/zigler/llms.txt Provides fine-grained control over type conversion between Zig and Elixir terms using `beam.get` and `beam.make`. This allows for custom marshalling logic, such as converting Elixir terms to Zig types and vice-versa, including custom atom and binary creation. ```elixir defmodule ManualMarshalling do use Zig, otp_app: :my_app, nifs: [process_term: [spec: false]] @spec process_term(integer()) :: integer() ~Z""" const beam = @import("beam"); pub fn process_term(term: beam.term) !beam.term { const value = try beam.get(i64, term, .{}); const result = value * 2 + 10; return beam.make(result, .{}); } pub fn make_custom_atom(str: []u8) !beam.term { return beam.make_into_atom(str, .{}); } pub fn make_binary(data: []const u8) !beam.term { return beam.make(data, .{.as = .binary}); } """ end # Usage ManualMarshalling.process_term(20) # => 50 ManualMarshalling.make_custom_atom("test") # => :test ManualMarshalling.make_binary([1, 2, 3]) # => <<1, 2, 3>> ``` -------------------------------- ### C Library Integration (BLAS) in Elixir Source: https://context7.com/e-xyza/zigler/llms.txt Wraps the BLAS C library to perform vector operations like sum and scaled addition. It links against the system 'blas' library and uses Zig's cImport to access BLAS functions. Inputs are typically slices of f64, and outputs are f64 or slices of f64. ```elixir defmodule BlasWrapper do use Zig, otp_app: :my_app, c: [link_lib: {:system, "blas"}] ~Z""" const blas = @cImport(@cInclude("cblas.h")); pub fn vector_sum(x: []f64) f64 { const n: c_int = @intCast(x.len); return blas.cblas_dasum(n, x.ptr, 1); } pub fn scale_add(a: f64, x: []f64, y: []f64) ![]f64 { if (x.len != y.len) return error.badarg; const n: c_int = @intCast(x.len); blas.cblas_daxpy(n, a, x.ptr, 1, y.ptr, 1); return y; } """ end # Usage BlasWrapper.vector_sum([1.0, 2.0, 3.0]) # => 6.0 BlasWrapper.scale_add(2.0, [1.0, 2.0], [3.0, 4.0]) # => [5.0, 8.0] ``` -------------------------------- ### Create BEAM tuples using Zig tuples Source: https://github.com/e-xyza/zigler/blob/main/guides/02-collections.md Illustrates how Zig tuples are treated as structs and can be converted to BEAM tuples using beam.make. The resulting tuple maintains the order and types of the original Zig tuple. ```elixir ~Z""" pub fn tuple() beam.term { return beam.make(.{.ok, 47}, .{}); } """ test "tuples" do assert {:ok, 47} == tuple() end ``` -------------------------------- ### Memory Tracking with Zig and C Allocators Source: https://github.com/e-xyza/zigler/blob/main/guides/03-allocators.md This demonstrates allocating large memory blocks using Zig's beam allocator (tracked by BEAM) versus C's malloc (untracked), storing in globals for illustration. Depends on beam and cImport for stdlib. Inputs: None (fixed 1MB size). Outputs: Void, but affects process memory. Limitations: Uses hidden globals, not recommended; better to use resources for real scenarios. ```zig var global_zigler: []u8 = undefined; pub fn zigler_alloc() !void { global_zigler = try beam.allocator.alloc(u8, 1_000_000); } pub fn zigler_free() void { beam.allocator.free(global_zigler); } const c_stdlib = @cImport(@cInclude("stdlib.h")); var global_cstd: [*c]u8 = undefined; pub fn c_malloc() void { global_cstd = @ptrCast(c_stdlib.malloc(1_000_000)); } pub fn c_free() void { c_stdlib.free(global_cstd); } ``` -------------------------------- ### Manually marshal types using beam.term in Zig Source: https://github.com/e-xyza/zigler/blob/main/guides/01-nifs.md Illustrates manual type marshalling using the beam.term opaque type for explicit control over BEAM term conversion. Requires importing the beam module and using beam.get() and beam.make() functions. Provides more flexibility at the cost of verbosity. ```zig const beam = @import("beam"); pub fn manual_addone(value_term: beam.term) !beam.term { const value = try beam.get(i32, value_term, .{}); return beam.make(value + 1, .{}); } ``` -------------------------------- ### Binding C BLAS Library in Zig NIF for Elixir Source: https://github.com/e-xyza/zigler/blob/main/README.md On Unix/Linux, links system BLAS library via c: [link_lib: {:system, "blas"}] and imports cblas.h in Zig. Performs vector addition (axpy) on f64 slices, checking length equality and returning error if mismatched. Outputs modified y slice; tested with sample vectors. ```elixir if {:unix, :linux} == :os.type() do defmodule Blas do use Zig, otp_app: :zigler, c: [link_lib: {:system, "blas"}] ~Z""" const beam = @import("beam"); const blas = @cImport({ @cInclude("cblas.h"); }); const BadArgs = error { badarg }; pub fn blas_axpy(a: f64, x: []f64, y: []f64) ![]f64 { if (x.len != y.len) return error.badarg; blas.cblas_daxpy(@intCast(x.len), a, x.ptr, 1, y.ptr, 1); return y; } """ end test "we can use a blas shared library" do # returns aX+Y assert [11.0, 18.0] == Blas.blas_axpy(3.0, [2.0, 4.0], [5.0, 6.0]) end end ``` ```zig const beam = @import("beam"); const blas = @cImport({ @cInclude("cblas.h"); }); const BadArgs = error { badarg }; pub fn blas_axpy(a: f64, x: []f64, y: []f64) ![]f64 { if (x.len != y.len) return error.badarg; blas.cblas_daxpy(@intCast(x.len), a, x.ptr, 1, y.ptr, 1); return y; } ``` -------------------------------- ### Zig Memory Management with Allocators Source: https://github.com/e-xyza/zigler/blob/main/guides/02-collections.md Demonstrates memory allocation and deallocation using Zig's `beam.allocator`. The `leaks` function shows a memory leak by returning an allocated struct without proper cleanup. The `no_leak` function illustrates correct memory management by using `defer beam.allocator.destroy` to ensure the allocated memory is freed. ```zig pub fn leaks() !*Point2D { var point = try beam.allocator.create(Point2D); point.x = 47; point.y = 50; return point; } pub fn no_leak() !beam.term { var point = try beam.allocator.create(Point2D); defer beam.allocator.destroy(point); point.x = 47; point.y = 50; return beam.make(point, .{}); } ``` -------------------------------- ### Resource Management with BEAM GC in Elixir Source: https://context7.com/e-xyza/zigler/llms.txt Defines a managed resource, Counter, that integrates with BEAM's garbage collector. It allows creating, incrementing, and retrieving the value of a counter resource using Zig within Elixir. Dependencies include the Zig and root modules from the beam library. ```elixir defmodule ResourceExample do use Zig, otp_app: :my_app, resources: [:Counter] ~Z""" const beam = @import("beam"); const root = @import("root"); const CounterData = struct { value: u64 }; pub const Counter = beam.Resource(CounterData, root, .{}); pub fn create_counter(initial: u64) !Counter { return Counter.create(.{.value = initial}, .{}); } pub fn increment(counter: Counter) u64 { var data = counter.unpack(); data.value += 1; return data.value; } pub fn get_value(counter: Counter) u64 { return counter.unpack().value; } """ end # Usage counter = ResourceExample.create_counter(10) # counter is a reference() ResourceExample.increment(counter) # => 11 ResourceExample.get_value(counter) # => 11 ``` -------------------------------- ### Configuring NIF Return Types (Binary, List) in Elixir Source: https://github.com/e-xyza/zigler/blob/main/guides/04-nif_options.md Configures NIF functions to return specific types like `:binary` or `:list` using the `return:` option within the `nifs` configuration. This ensures the function's typespec correctly reflects the return type. It utilizes the `Zig` macro and `ExUnit.Case` for testing. ```elixir defmodule ReturnTypeTest do use ExUnit.Case, async: true use Zig, otp_app: :zigler, nifs: [ returns_binary: [return: :binary], returns_list: [return: :list] ] ~Z""" pub fn returns_binary() [3]u16 { return [3]u16{47, 48, 49}; } pub fn returns_list() []const u8 { return "Hello world!"; } """ test "returns binary" do assert <<47, 0, 48, 0, 49, 0>> = returns_binary() end test "returns list" do assert ~C'Hello world!' = returns_list() end end ``` -------------------------------- ### Include External Zig Modules with extra_modules in Zigler Source: https://github.com/e-xyza/zigler/blob/main/guides/08-module_options.md Illustrates how to incorporate external Zig files as modules within your Zigler project using the `extra_modules` option. This allows for modularity and code reuse by specifying the module name, file path, and its dependencies. ```elixir defmodule PackageFile do use ExUnit.Case, async: true use Zig, otp_app: :zigler, extra_modules: [extra: {"./test/_support/module/extra.zig", [:beam]}] ~Z""" const extra = @import("extra"); pub fn extra_value() u64 { return extra.value; } """ test "module file" do assert 47 = extra_value() end end ``` ```zig pub const value = 47; ``` -------------------------------- ### Struct Return Types as Maps in Zig Source: https://context7.com/e-xyza/zigler/llms.txt Return Zig structs as Elixir maps with automatic field conversion. This simplifies data exchange between Zig and Elixir by leveraging Zig's struct definitions and Elixir's map syntax. It handles basic types like f64, u8, and bool. ```elixir defmodule StructReturn do use Zig, otp_app: :my_app ~Z""" const Point = struct { x: f64, y: f64 }; pub fn create_point(x: f64, y: f64) Point { return .{ .x = x, .y = y }; } pub fn midpoint(p1: Point, p2: Point) Point { return .{ .x = (p1.x + p2.x) / 2.0, .y = (p1.y + p2.y) / 2.0 }; } const Person = struct { name: []const u8, age: u32, active: bool }; pub fn create_person(name: []const u8, age: u32) Person { return .{ .name = name, .age = age, .active = true }; } """ end # Usage point = StructReturn.create_point(3.0, 4.0) # => %{x: 3.0, y: 4.0} StructReturn.midpoint(point, %{x: 7.0, y: 8.0}) # => %{x: 5.0, y: 6.0} StructReturn.create_person("Alice", 30) # => %{name: "Alice", age: 30, active: true} ``` -------------------------------- ### Handle packed and extern structs as binaries Source: https://github.com/e-xyza/zigler/blob/main/guides/02-collections.md Demonstrates how packed and extern Zig structs can be passed as Elixir binaries or maps. Packed structs are memory-efficient representations, while extern structs follow C ABI. Note endianness concerns with packed/extern structs. ```elixir ~Z""" pub const Packed = packed struct {x: u4, y: u4}; pub const Extern = extern struct {x: u16, y: u16}; pub fn diff_packed(value: Packed) u8 { return value.x - value.y; } pub fn diff_extern(value: Extern) u16 { return value.x - value.y; } """ test "packed and extern structs as struct" do assert 2 = diff_packed(%{x: 7, y: 5}) assert 5 = diff_extern(%{x: 47, y: 42}) end test "packed and extern structs as binary" do assert 2 = diff_packed(<<0x57>>) assert 5 = diff_extern(<<47::unsigned-size(16)-native, 42::unsigned-size(16)-native>>) end ```