Starlark for Elixir
Starlark embeds the Bazel-compatible Starlark language in Elixir by pairing a tiny wrapper module with a Rustler-powered NIF. It lets you run untrusted scripts with resource limits, capture stdout, exports, and notifications, and even make safe round-trips into Elixir by binding regular functions.
Installation
Add the dependency to your mix.exs (the package name will remain :starlark on Hex):
def deps do
[
{:starlark, "~> 0.1"}
]
end
Make sure you have a working Rust toolchain (rustup or equivalent). The NIF is compiled automatically when you run mix deps.get followed by mix compile.
Quick start
iex> {:ok, result} =
...> Starlark.eval("""
...> load("@stdlib//json", "json")
...>
...> def average(latency_samples):
...> total = 0
...> for value in latency_samples:
...> total = total + value
...> return total / len(latency_samples)
...>
...> notify("checks", json.encode({"avg": average(samples)}))
...> average(samples)
...> """,
...> bindings: %{samples: [90, 110, 100]},
...> function_bindings: %{log: &Logger.info/1}
...> )
iex> result.value
100
iex> result.notifications
[%Starlark.Notification{channel: "checks", message: "{\"avg\": 100.0}"}]
Every successful call returns %Starlark.Result{} whose fields include:
value/value_repr– JSON-friendly value and the underlying Starlark representation.stdout– any output produced byprint.exports– variables set viaset_var/2.notifications– messages emitted fromnotify/2.http_calls– metadata captured for eachhttp_get/1.
Failures yield {:error, %Starlark.EvalError{}} with a descriptive :kind (:parse, :runtime, :timeout, :resource_limit, :io, etc.).
Binding Elixir functions
Expose host functionality safely by passing a :function_bindings map. Each function executes in the caller process; the runtime applies JSON encoding/decoding automatically.
double = fn value -> value * 2 end
script = """
def greet(name):
return "Hello from Starlark, " + name
result = double(counter)
set_var("greeting", greet(user))
result
"""
{:ok, result} =
Starlark.eval(script,
bindings: %{counter: 21, user: "root"},
function_bindings: %{double: double}
)
result.value
# => 42
result.exports["greeting"]
# => "Hello from Starlark, world"
The dispatcher checks that each published function matches the arity advertised in function_bindings and propagates any exceptions back into the script as runtime errors.
Evaluating scripts from disk
Prefer eval_file/2 when you manage scripts on disk:
case Starlark.eval_file("priv/scripts/check.star", wall_time_ms: 2_000) do
{:ok, %Starlark.Result{} = result} ->
IO.inspect(result.exports)
{:error, %Starlark.EvalError{kind: :io} = error} ->
Logger.error(error.message)
{:error, %Starlark.EvalError{} = error} ->
Logger.error("Script failed: #{error.message}")
endeval_file/2 returns {:error, %EvalError{kind: :io}} with the formatted reason when the file cannot be read.
Resource limits
The evaluator exposes several guardrails for running untrusted code:
:max_steps– caps the number of executed statements.:max_call_depth– prevents runaway recursion.:max_heap_bytes– enforces a heap budget (checked before and after statements).:wall_time_ms– aborts scripts that exceed a wall-clock deadline.:http_timeout_ms– per-request timeout forhttp_get/1.
All options are optional and default to the conservative values baked into the Rust runtime.
Notifications and HTTP
Two helper functions are available inside scripts:
notify(channel, message)– append a notification recorded in%Result.notifications.http_get(url)– performs an HTTP GET with an optional timeout. The body is returned to the script and each call is logged to%Result.http_calls.
Transport requirements
http_get/1 relies on reqwest with rustls. No additional configuration is required on the Elixir side, but the target system must ship with the standard OS TLS roots.
Development
-
Run
mix deps.getthenmix compileto build the NIF. -
Execute the test suite with
mix test(integration tests cover bindings, limits, notifications, and I/O failures). -
Generate HTML docs via
mix docs.
When publishing to Hex, run mix hex.build to verify the package metadata and included files.