Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Two ways to reach a kernel

Linked in

The default, and the right default. Your crate exposes a space(); the host takes a Cargo dependency on it and chains its bindings onto its own:

ikigai_fn::space().bind(Exact::new("urn:iki:tutorial:camel-case"), camel_case())

The endpoints are in the binary. Resolution is a function call away. There is no marshalling, no versioning question at runtime, and nothing to go wrong at load time because there is no load time.

Every endpoint in the ikigai CLI arrives this way. The CLI does not depend on ikigai-module at all.

Routed to a module

A module is compiled separately. The host does not name its endpoints and does not know what they are; it routes a prefix of the name space to a transport and lets the module answer:

/// A resource the **host** owns. The module cannot reach this except by asking.
pub fn host_name() -> AsyncFnEndpoint {
    AsyncFnEndpoint::new("host-name", |_inv: &Invocation<'_>| -> InvokeFuture<'_> {
        Box::pin(async move { Ok(Representation::new(text_plain_utf8(), b"Peter".to_vec())) })
    })
    .with_description(
        Description::new("host-name")
            .title("Who the host is")
            .verb(Verb::Source)
            .verb(Verb::Meta),
    )
}

/// The host's root space: its own resources, plus a [`ModuleSpace`] that routes
/// everything under `urn:greet:` to the module.
///
/// `ModuleSpace` implements `Space`, so it sits in the `Fallback` exactly where a
/// statically linked `space()` would — the host does not have a special case for
/// "modules", it has one more space.
///
/// The third argument is the mount's **floor**: the capability every request through
/// this mount must satisfy before the module is even consulted. `ModuleFloor::public()`
/// is no floor at all, which is right for a demo — and since ikigai-module 0.2.0 it is
/// not the only gate: an endpoint's own declared `requires` is enforced across the
/// boundary too, exactly as it is for a linked-in one. The test below proves that.
pub fn host_space() -> Fallback {
    let module = InProcessTransport::new(module_space());

    Fallback::new(vec![
        Arc::new(EndpointSpace::new().bind(Exact::new("urn:host:name"), host_name()))
            as Arc<dyn Space>,
        Arc::new(ModuleSpace::new(
            ["urn:greet:"],
            Arc::new(module),
            ModuleFloor::public(),
        )) as Arc<dyn Space>,
    ])
}

Two things to notice, because they are the reason this composes at all.

ModuleSpace implements Space. It goes into the Fallback exactly where a statically linked space() would go. The host has no special case for “modules” — it has one more space, tried in order like the others.

The host binds nothing under urn:greet:. It has no Exact for urn:greet:hello, and could not write one without knowing what the module offers. Prefix routing is what lets a host delegate a region of the name space to code it has never seen.

When each is right

Link it in when you can. It is simpler, faster, and has fewer failure modes.

Reach for a module when one of these is true:

  • The dependency is expensive and rarely used. ikigai-xslt exists as a module precisely so that its XSLT engine is not linked into every host that will never transform an XML document.
  • The host cannot link it. A kernel running as WebAssembly in a browser page cannot grow a new statically linked space; it can fetch one.
  • The code arrives after the host was built. Which is the whole point, and also the part that is not finished — see Where this actually stands.