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>(&mut self, schemas: I)where
I: IntoIterator<Item = ModuleSchema>,
fn extend<I>(&mut self, schemas: I)where
I: IntoIterator<Item = ModuleSchema>,
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>(schemas: I) -> HostSchemaswhere
I: IntoIterator<Item = ModuleSchema>,
fn from_iter<I>(schemas: I) -> HostSchemaswhere
I: IntoIterator<Item = ModuleSchema>,
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 ==.impl StructuralPartialEq for ModuleSchema
Auto Trait Implementations§
impl Freeze for ModuleSchema
impl RefUnwindSafe for ModuleSchema
impl Send for ModuleSchema
impl Sync for ModuleSchema
impl Unpin for ModuleSchema
impl UnsafeUnpin for ModuleSchema
impl UnwindSafe for ModuleSchema
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.