Skip to content

GenServer

OTP is a standard-library surface, not special compiler syntax. A server is an ordinary module implementing gale_std.gen_server.Server. Gale emits the native callback module with @behaviour GenServer and explicit @impl GenServer attributes; it does not emit use GenServer.

module genserver
implements gale_std.gen_server.Server<
Integer,
Integer,
CounterCall,
CounterCast,
Never,
Never
> as counter
pub type CounterCall =
| Value(reply_to: gale_std.gen_server.ReplyTo<Integer>)
| Add(amount: Integer, reply_to: gale_std.gen_server.ReplyTo<Integer>)
pub type CounterCast = Reset | Increment
pub type CounterTarget = gale_std.gen_server.ServerTarget<counter>
pub fn start_link(initial: Integer) -> gale_std.gen_server.StartResult<counter> {
gale_std.gen_server.start_link(genserver, initial, [])
}
pub fn child_spec(initial: Integer) -> ChildSpec<counter> {
gale_std.gen_server.child_spec(genserver, initial)
}
pub fn value(server: CounterTarget) -> Integer {
gale_std.gen_server.call(server, fn(reply_to) => Value(reply_to))
}
pub fn add(server: CounterTarget, amount: Integer) -> Integer {
gale_std.gen_server.call(server, fn(reply_to) => Add(amount, reply_to))
}
pub fn reset(server: CounterTarget) -> :ok {
gale_std.gen_server.cast(server, Reset)
}
pub fn increment(server: CounterTarget) -> :ok {
gale_std.gen_server.cast(server, Increment)
}
pub fn init(initial: Integer) -> gale_std.otp.Init<Integer, Never> {
(:ok, initial)
}
pub fn handle_call<R>(
build: fn(gale_std.gen_server.ReplyTo<R>) -> CounterCall,
from: gale_std.gen_server.ReplyTo<R>,
state: Integer
) -> gale_std.otp.Next<Integer, Never> {
match build(from) {
Value(reply_to) -> {
gale_std.gen_server.reply(reply_to, state)
(:noreply, state)
}
Add(amount, reply_to) -> {
let next = state + amount
gale_std.gen_server.reply(reply_to, next)
(:noreply, next)
}
}
}
pub fn handle_cast(
message: CounterCast,
state: Integer
) -> gale_std.otp.Next<Integer, Never> {
match message {
Reset -> (:noreply, 0)
Increment -> (:noreply, state + 1)
}
}

Calls return their native reply directly and exit on timeout or server failure. The builder closure is the construction that lets Gale associate each call constructor with the exact ReplyTo<R> it must answer. Casts and ordinary messages remain separate native channels.

child_spec/1 is ordinary typed Gale code replacing the convenience that use GenServer would inject. That keeps Elixir’s {GenServer, initial} supervisor form interoperable without invoking a macro.

match genserver.start_link(10) {
(:ok, server) -> {
let _ = genserver.increment(server)
let _ = gale_std.test.assert(genserver.value(server) == 11)
let _ = gale_std.test.assert(genserver.add(server, 4) == 15)
let _ = genserver.reset(server)
gale_std.test.assert(genserver.value(server) == 0)
}
:ignore -> gale_std.test.assert(false)
(:error, _) -> gale_std.test.assert(false)
}