Powered by AppSignal & Oban Pro

Userdata

guides/examples/userdata.livemd

Userdata

Mix.install([
  {:lua, "~> 1.0.0-rc.0"}
])

Passing an Elixir struct to Lua

Userdata lets you hand an Elixir term to Lua as an opaque reference. Inside Lua the value cannot be indexed or mutated — it can only be passed back to Elixir-defined functions. Structs travel through Lua as {:userdata, term}.

Here we define a Counter struct and expose it as a global counter.

defmodule Counter do
  defstruct count: 0
end

{counter_ref, lua} = Lua.encode!(Lua.new(), {:userdata, struct(Counter, count: 0)})
lua = Lua.set!(lua, [:counter], counter_ref)
:ok
:ok

Defining methods in Elixir

Native functions registered with Lua.set!/3 use the 2-arity form fn args, state -> {results, state} end so they can Lua.decode!/2 the reference back into the struct and Lua.encode!/2 a new one to return.

Counter.inc/1 returns a new userdata reference with count + 1, and Counter.value/1 reads the count out as a plain integer.

lua =
  Lua.set!(lua, [:Counter, :inc], fn [ref], state ->
    {:userdata, c} = Lua.decode!(state, ref)
    {new_ref, state} = Lua.encode!(state, {:userdata, %{c | count: c.count + 1}})
    {[new_ref], state}
  end)

lua =
  Lua.set!(lua, [:Counter, :value], fn [ref], state ->
    {:userdata, c} = Lua.decode!(state, ref)
    {[c.count], state}
  end)

:ok
:ok

Calling the methods from Lua

{[result], _lua} =
  Lua.eval!(lua, """
  counter = Counter.inc(counter)
  counter = Counter.inc(counter)
  return Counter.value(counter)
  """)

result
2