pub struct ModuleSchema {
pub name: &'static str,
pub capability: &'static str,
pub operations: &'static [OperationSchema],
pub types: &'static [TypeSchema],
pub resources: &'static [ResourceSchema],
}Expand description
The name, capability, operations, types, and resources of one host module.
This is the whole of what a module declares about itself, detached from
any module: cove-sema reads it with no host to ask and no runtime to
depend on, and cove trace and cove replay read it with nothing running
at all. A live module is asked through HostApi, whose answers are these
same tables.
§A schema assembled at run time is built once and leaked
Every field here is &'static, and so is every payload a HostType
inside one points at. A schema written as a const — every module the
toolchain ships, and every one in the tests — costs nothing for that: it
was in the binary already, and being Copy is what lets a caller hold a
schema while it goes on reading whatever it asked.
A module whose shape is only known once the process is running pays,
though. A name from configuration, operations from a plugin manifest,
resources from a table list discovered at connect time — none of that is
'static, and HostApi::module_schema hands the table back by value, so
there is nowhere to borrow from. Such a host assembles its table once, the
first time it is asked, leaks it, and hands out the same copy afterwards:
struct Plugin {
/// The operations the manifest named, read once at startup.
manifest: Vec<String>,
/// The table they describe, assembled on the first ask and no other.
schema: OnceLock<ModuleSchema>,
}
impl Plugin {
fn module_schema(&self) -> ModuleSchema {
*self.schema.get_or_init(|| ModuleSchema {
name: "plugin",
capability: "plugin",
operations: Vec::leak(
self.manifest
.iter()
.map(|name| OperationSchema {
name: String::leak(name.clone()),
params: &[HostType::String],
variadic: false,
result: HostType::Result(&HostType::String, &HostType::Error),
capability: "plugin",
effect: Effect::Read,
cancellable: false,
recordable: true,
result_is_task_safe: true,
})
.collect::<Vec<_>>(),
),
types: &[],
resources: &[],
})
}
}The OnceLock is the whole of the discipline, and
it is what makes the cost a bounded one. A handful of allocations per
module for the life of a process is what an in-process embedding
registered at startup pays, once; a host that assembles its table inside
module_schema instead pays it again on every call the registry
dispatches, which is not bounded by anything.
crates/cove-runtime/tests/embedding.rs runs the pattern end to end and
asserts the bound.
§Why the fields are &'static and not &'a
Issue #86 asked for
ModuleSchema<'a> and called it the principled fix. It is not a fix. It
would spread a lifetime through every crate that names this type and
leave the leak where it was, because neither of the two things a host
could borrow a schema from is available to it.
It cannot borrow from itself. A host holding names: Vec<String> beside
operations: Vec<OperationSchema<'a>> needs 'a to be the lifetime of
the field next to it, which is a self-referential struct and not
something safe Rust builds.
It cannot borrow from anything longer-lived either, because a registered
module is a Box<dyn HostApi>, which is Box<dyn HostApi + 'static>. It
has to be: ADR 0008 gives every spawned task a thread of its own,
std::thread::Builder::spawn takes a 'static closure, and that closure
holds the Arc<Runtime> that holds the registry. A registry that
borrowed its modules would be a run that could not spawn a task.
The representation that would remove the leak is the other one: a
schema that owns what it describes, so a host keeps one in a field and
hands back &self.schema. What rules it out is not the reason issue #86
gives. Cow::Borrowed is const-constructible, so the shipped tables
could stay const — though the recursive HostType payloads would
need a hand-written Static | Shared pair beside it, because
Cow<'static, HostType> is a layout cycle. What rules it out is the
price. Some 260 fields across hosts.rs stop being written as literals
and start being written as constructor calls, in a table that is
hand-written because being read by hand is the point of it. Copy
goes, and with it the shape of every reader that holds a schema while it
goes on working: HostRegistry::host_type hands the interpreter an entry
rather than a borrow precisely because the interpreter is about to
evaluate arguments, which it cannot do while borrowing the registry. And
a clone of an owned half is a deep copy where a copy of a static one was
free. A trait with two implementations pays the same noise for a dynamic
call on every read, and gives one description two vocabularies — the
drift this crate exists to prevent.
So the tables stay literals, the readers stay Copy, and the leak
stays: bounded, documented here, and exercised by a test.
Fields§
§name: &'static strThe name Cove source uses, such as console.
capability: &'static strThe capability a host must grant for this module.
A capability is a plain name here rather than cove_sema::Capability,
because that type belongs to the crate that reads cove.toml and this
one sits below it.
operations: &'static [OperationSchema]Every operation the module exposes.
types: &'static [TypeSchema]Every type the module declares.
resources: &'static [ResourceSchema]Every kind of resource the module can open.
Implementations§
Source§impl ModuleSchema
impl ModuleSchema
Sourcepub fn operation(&self, name: &str) -> Option<&'static OperationSchema>
pub fn operation(&self, name: &str) -> Option<&'static OperationSchema>
The operation name, if this module exposes one.
Sourcepub fn declared_type(&self, name: &str) -> Option<&'static TypeSchema>
pub fn declared_type(&self, name: &str) -> Option<&'static TypeSchema>
The type name, if this module declares one that is plain data.
Sourcepub fn resource(&self, name: &str) -> Option<&'static ResourceSchema>
pub fn resource(&self, name: &str) -> Option<&'static ResourceSchema>
The kind of resource name, if this module can open one.
Sourcepub fn declares_type(&self, name: &str) -> bool
pub fn declares_type(&self, name: &str) -> bool
Whether this module declares name as a type of its own, either as
plain data or as a resource it keeps.
The two are one question wherever a name is being read rather than
used: http.Response and http.Server are both written the same way
in a signature, and which of them the host keeps is the host’s
business.
Sourcepub fn validate(&self) -> Result<(), SchemaFault>
pub fn validate(&self) -> Result<(), SchemaFault>
Whether every type this module declares is one some value could be.
There is one way to write a type that nothing can satisfy, and
HostType::Set and HostType::Map are what introduced it: a Set
element and a Map key have to satisfy Cove’s MapKey restriction,
and HostType::may_be_a_key says which declarations do.
It is checked here, where a schema is read, rather than at the
boundary where a value is. A Set<reviews.PullRequest> the boundary
refused would be refused on the first call that carried one, in
production, in whichever operation happened to come first — which is
the failure mode ADR 0017 moved a Host API description out of the
runtime to prevent. Read here it is one sentence naming the field.
Every table this workspace ships is held to this by
cove_schema::hosts’s own tests. An embedder’s table is the
embedder’s, so an embedder calls this on it — one assertion in the test
that already exists is enough, and
examples/rules/host/tests/embedding.rs is where that is written down.
Trait Implementations§
Source§impl Clone for ModuleSchema
impl Clone for ModuleSchema
Source§fn clone(&self) -> ModuleSchema
fn clone(&self) -> ModuleSchema
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for ModuleSchema
Source§impl Debug for ModuleSchema
impl Debug for ModuleSchema
impl Eq for ModuleSchema
Source§impl Extend<ModuleSchema> for HostSchemas
impl Extend<ModuleSchema> for HostSchemas
Source§fn extend<I: IntoIterator<Item = ModuleSchema>>(&mut self, schemas: I)
fn extend<I: IntoIterator<Item = ModuleSchema>>(&mut self, schemas: I)
Source§fn extend_one(&mut self, item: A)
fn extend_one(&mut self, item: A)
extend_one)Source§fn extend_reserve(&mut self, additional: usize)
fn extend_reserve(&mut self, additional: usize)
extend_one)Source§impl FromIterator<ModuleSchema> for HostSchemas
impl FromIterator<ModuleSchema> for HostSchemas
Source§fn from_iter<I: IntoIterator<Item = ModuleSchema>>(schemas: I) -> HostSchemas
fn from_iter<I: IntoIterator<Item = ModuleSchema>>(schemas: I) -> HostSchemas
Source§impl PartialEq for ModuleSchema
impl PartialEq for ModuleSchema
Source§fn eq(&self, other: &ModuleSchema) -> bool
fn eq(&self, other: &ModuleSchema) -> bool
self and other values to be equal, and is used by ==.