Concepts

Modules Prototype

A module is the unit you add to an application. It says who it is, what it needs and what it offers, using ordinary trait implementations.

Identity

Every module has a stable, human-readable identity. Diagnostics, inspection output and explicit provider selection all refer to it.

use rustclamp_core::{Module, ModuleId};

pub struct GreeterModule;

impl Module for GreeterModule {
    const ID: ModuleId = ModuleId::new("example.module.greeter");
}

Requires and Provides

Relationships are additive: each one is a separate trait impl, so a module grows by adding declarations, not by editing a central list (ADR 0004).

pub struct FixedClockModule(pub SystemTime);

impl Module for FixedClockModule {
    const ID: ModuleId = ModuleId::new("example.module.clock");
}

impl Clock for FixedClockModule {
    fn now(&self) -> SystemTime {
        self.0
    }
}

impl Provides<ClockCapability> for FixedClockModule {
    fn provided_value(&self) -> &(dyn Clock + 'static) {
        self
    }
}

impl Requires<ClockCapability> for GreeterModule {}

Requires carries no body: the declaration is the point. Kernel reads it to find a provider, reports it in diagnostics, and uses it to order startup.

Resolution

Kernel adapts the declarations into typed provisions and requirements and resolves them:

let provisions = [Provision::<ClockCapability>::from_module(&clock)];
let requirement = CapabilityRequirement::<ClockCapability>::from_module::<GreeterModule>();
let clock = Resolver::resolve::<ClockCapability>(
    requirement.required_by(),
    &provisions,
    requirement.selected_provider(),
)?;
SituationResult
One providerResolves directly.
No providerMissing-provider error naming the module and the capability.
Two providersAmbiguity error with the candidates sorted — the same in either registration order.
Explicit selectionThe selected provider wins; selecting an unavailable one lists what is available.
CycleReported as a closed module path; validated iteratively up to 2,048 modules without stack growth.

What a module does not do

  • It does not register itself in a global. There is no registry, TypeId map or Any lookup.
  • It does not need Send, Sync, Arc or 'static. Provisions borrow their provider; a non-thread-safe Rc<Cell<_>> clock resolves fine on the calling thread.
  • It does not depend on the facade. A module built only against Core and Kernel compiles and tests on its own.

In the design, not the code yet

Modules will also participate in lifecycle contracts (initialize, start, stop) and declare which resources they own. The lifecycle prototype explores the first; resource ownership is not implemented. Planned