Skip to content

Externs

An extern declaration tells Gale how to call or describe code outside Gale. The declaration is a trusted contract: Gale checks its callers against the signature, but cannot prove that the native implementation keeps that promise.

Typed wrapper for a native text function. Gale callers use clean or trim.

mod externs.text
pub extern "String.trim" trim(value: String) -> String
pub fn clean(value: String) -> String {
trim(value)
}

Gale emits a function in Externs.Text with a spec and a body that calls String.trim(value). Other Gale code calls that function. The native call keeps its full module name in generated Elixir; a Gale alias is resolved before emission and cannot redirect it. Erlang functions use spellings such as ":erlang.send".

In test and development builds, Gale checks arguments and return values at Gale function boundaries. Production builds omit those checks. The declared types are your responsibility when the native function returns a shape Gale cannot infer.

If a native function may raise an expected exception, a raises clause turns that exception into Error while a normal return becomes Ok:

Expected native exceptions become typed Result errors.

mod externs.numbers
pub extern exception "ArgumentError" ArgumentError {
message: String
}
pub extern "String.to_integer" to_integer(value: String) -> Integer
raises ArgumentError

The Gale return type is Result<Integer, ArgumentError>. Other exceptions are reraised; exits and throws are not caught.

Use extern type for a named foreign value whose structure Gale does not describe. It is nominal, but gives Gale no fields to read or recursively validate. Here Range.new is the native constructor:

A native range stays opaque in Gale and is built by its native constructor.

mod externs.range
pub extern type Range
pub extern "Range.new" new(first: Integer, last: Integer) -> Range

Use extern struct when Gale should know fields of a native Elixir struct. An extern exception is also a nominal native struct type. Gale can match declared fields and use it in raises, but cannot construct or update it.

The example adds {:plug, "~> 1.20"} to mix.exs. Plug.Test.conn creates an in-memory request connection, so the tests make no HTTP calls. One Gale module binds its functions, connection struct, exception, and module value:

Plug is a Mix dependency. Gale binds its functions, native connection struct, and already-sent exception.

mod externs.plug
pub extern struct "Plug.Conn" Conn {
method: String,
request_path: String,
status: Nilable<Integer>,
state: Atom
}
pub extern exception "Plug.Conn.AlreadySentError" AlreadySentError {
message: String
}
pub extern mod "Plug.Conn" conn_module
pub extern "Plug.Test.conn" test_conn(method: String, path: String) -> Conn
pub extern "Plug.Conn.send_resp" send_resp(
conn: Conn,
status: Integer,
body: String
) -> Conn raises AlreadySentError
pub fn native_module() -> Term {
conn_module
}

Conn is a Gale type, emitted as @type conn() :: %Plug.Conn{...}. Function specs use conn() or Externs.Plug.conn() even though Plug defines Plug.Conn.t(). Values remain native %Plug.Conn{} structs: Gale emits no replacement defstruct and makes no copy. Typed field access, patterns, construction, and updates use the native struct.

At checked Gale function boundaries, the runtime verifies the native struct module and recursively checks every declared field. Undeclared native fields are allowed. Nilable<T> means T | nil, as with the connection’s status before a response is sent. Construction requires every declared field; native defstruct defaults fill undeclared fields. Native @enforce_keys and constructor rules still apply, so prefer native constructors such as Plug.Test.conn when they establish invariants. Extern structs have no type parameters in this version, and a module may declare several of them.

send_resp returns Result<Conn, AlreadySentError> in Gale. Calling it on an already sent connection produces Error with the original native exception.

extern mod "Plug.Conn" conn_module names the native module as a value. It does not import or expose any of Plug’s functions; each called function needs its own extern signature. You need extern mod only when passing a native module value through Gale code. An implements clause can grant that value a declared behaviour capability.

extern "GenServer" declares Server<...> maps Gale callback signatures to a native BEAM behaviour. See Behaviours for the callback and implementation rules.

For exact syntax and projection rules, see the language reference and type system.