Capabilities

The capability system lets you express resource requirements in function types. A module that declares needs IO can only be used by code that also holds that capability — enforced at compile time with no runtime cost.


Declaring requirements

needs lists the capabilities a function requires:

needs IO
fn write_log(msg : String) : Result((), String) do
  File.append("app.log", msg ++ "\n")
end

A caller that doesn’t declare needs IO will get a compile error if it tries to call write_log. The requirement propagates upward through the call graph automatically.


Proof capabilities

proof cap declares a capability that can only be created inside one module — useful for authority tokens. Minting goes through the gated mint_cap builtin, which only typechecks inside a public function of the declaring module and takes the ambient Cap(IO) (or a narrower cap) to authorize the mint. Proof-cap types are always referred to by their qualified Module.Name form, even from inside the declaring module:

mod Admin do
  proof cap AdminCap
  needs IO
  needs Admin.AdminCap

  fn make_cap(io : Cap(IO)) : Cap(Admin.AdminCap) do
    mint_cap(io)
  end

  fn delete_all_users(_admin : Cap(Admin.AdminCap)) : Result((), String) do
    Db.execute("DELETE FROM users")
  end
end

Outside Admin, no code can manufacture a Cap(Admin.AdminCap). It can only pass one through that it received from Admin.make_cap(). The capability becomes an unforgeable proof of authorization.


Realtime exclusion

Tagged(X, Realtime) marks a computation as realtime-safe. Calling Cap(Alloc), Cap(IO), or Cap(Panic) inside a realtime-tagged function is a compile error:

fn process_sample(buf : Tagged(Buffer, Realtime)) : Tagged(Buffer, Realtime) do
  -- allocating here would be a compile error
  transform(buf)
end

This statically prevents audio/video processing code from accidentally allocating or blocking.


Complete example: sandboxed plugin runner

mod PluginDemo do
  mod Plugin do
    proof cap PluginCap
    needs IO
    needs Plugin.PluginCap

    fn grant(io : Cap(IO)) : Cap(Plugin.PluginCap) do
      mint_cap(io)
    end

    -- Stand-in for a real sandboxed evaluator — the point of this example is
    -- the capability gate around `run`, not the evaluation strategy itself.
    fn sandbox_eval(code : String) : Result(String, String) do
      Ok("evaluated: " ++ code)
    end

    fn run(_cap : Cap(Plugin.PluginCap), code : String) : Result(String, String) do
      sandbox_eval(code)
    end
  end

  mod Main do
    needs IO
    needs Plugin.PluginCap

    fn main(root : Cap(IO)) do
      let cap = Plugin.grant(root)
      -- `cap` must be threaded explicitly into `run` below — capabilities
      -- are ordinary values passed as arguments, not ambient state; there is
      -- no way to manufacture one without going through `Plugin.grant`
      match Plugin.run(cap, "1 + 1") do
        Ok(v)  -> println("result: " ++ v)
        Err(e) -> println("error: " ++ e)
      end
    end
  end
end

The PluginCap ensures that only code explicitly granted the capability can invoke the sandbox runner. Untrusted code paths can never call Plugin.run — not because of a runtime check, but because the type won’t compile without the cap.

march — interactive
Click run on any snippet to try it here.
march>