Skip to main content

geam_core/host/
external.rs

1mod dynamic;
2mod store;
3mod stored;
4
5use crate::host::{HostProfile, HostProvider, HostTypeListEnd};
6use ecow::EcoString;
7use std::marker::PhantomData;
8
9pub(crate) use crate::provider_support::HostStoredValueFamily;
10pub use dynamic::HostStoredDynamic;
11pub use store::HostExternalStore;
12pub(crate) use store::{ExternalPayloadLease, ExternalPayloadView};
13pub use stored::{
14    HostExternalPayloadBuilder, HostExternalPayloadView, HostStoredType, HostStoredValue,
15};
16
17/// A source-declared external Gleam type linked to Rust storage.
18pub trait HostExternalSchema: Send + Sync + 'static {
19    const PACKAGE: &'static str;
20    const MODULE: &'static str;
21    const NAME: &'static str;
22    const PARAMETER_COUNT: usize;
23}
24
25/// Provider-owned storage and Gleam source semantics for one external schema.
26///
27/// Payloads are immutable after creation. Values that compare equal through
28/// [`HostExternalStorage::source_equal`] must return the same
29/// [`HostExternalStorage::source_hash`]. Hash collisions are allowed and are
30/// resolved through source equality. Source hashes are runtime indexes, not
31/// stable serialized values.
32pub trait HostExternalStorage<Profile, Schema>: Send + Sync + 'static
33where
34    Profile: HostProfile,
35    Schema: HostExternalSchema,
36{
37    type Payload: 'static;
38
39    /// Projects the typed payload store from the final profile's external stores.
40    fn store(stores: &Profile::ExternalStores) -> &HostExternalStore<Self::Payload>;
41
42    /// Compares two payloads using Gleam source equality.
43    fn source_equal(
44        context: &HostExternalEquality<'_>,
45        left: &Self::Payload,
46        right: &Self::Payload,
47    ) -> bool;
48
49    /// Hashes a payload consistently with [`HostExternalStorage::source_equal`].
50    fn source_hash(context: &HostExternalHashing<'_>, value: &Self::Payload) -> u64;
51
52    /// Produces the payload's canonical source-oriented inspection.
53    fn inspect(context: &HostExternalInspection<'_>, value: &Self::Payload) -> EcoString;
54}
55
56/// Selects a provider-owned external storage adapter for one source schema.
57pub trait HostExternalBinding<Profile, Schema>: HostProvider<Profile>
58where
59    Profile: HostProfile,
60    Schema: HostExternalSchema,
61{
62    type Storage: HostExternalStorage<Profile, Schema>;
63}
64
65/// Gleam source equality for values retained by an external payload.
66pub struct HostExternalEquality<'context> {
67    equal: &'context dyn Fn(
68        &crate::runtime::StoredRuntimeValue,
69        &crate::runtime::StoredRuntimeValue,
70    ) -> bool,
71}
72
73/// Gleam source hashing for values retained by an external payload.
74pub struct HostExternalHashing<'context> {
75    source_hash: &'context dyn Fn(&crate::runtime::StoredRuntimeValue) -> u64,
76}
77
78/// Canonical inspection for values retained by an external payload.
79pub struct HostExternalInspection<'context> {
80    inspect: &'context dyn Fn(&crate::runtime::StoredRuntimeValue) -> EcoString,
81}
82
83/// A source-declared external type and its concrete type arguments.
84pub struct HostExternalType<Schema, Arguments = HostTypeListEnd>(PhantomData<(Schema, Arguments)>);
85
86impl<'context> HostExternalEquality<'context> {
87    pub(crate) fn new(
88        equal: &'context dyn Fn(
89            &crate::runtime::StoredRuntimeValue,
90            &crate::runtime::StoredRuntimeValue,
91        ) -> bool,
92    ) -> Self {
93        Self { equal }
94    }
95
96    /// Compares two retained values with their exact sealed host type.
97    pub fn stored_values_equal<Type>(
98        &self,
99        left: &HostStoredValue<Type>,
100        right: &HostStoredValue<Type>,
101    ) -> bool {
102        (self.equal)(&left.value, &right.value)
103    }
104
105    /// Compares two existentially retained values using their specialized shapes.
106    pub fn dynamic_values_equal(
107        &self,
108        left: &HostStoredDynamic,
109        right: &HostStoredDynamic,
110    ) -> bool {
111        (self.equal)(left.runtime_value(), right.runtime_value())
112    }
113}
114
115impl<'context> HostExternalHashing<'context> {
116    pub(crate) fn new(
117        source_hash: &'context dyn Fn(&crate::runtime::StoredRuntimeValue) -> u64,
118    ) -> Self {
119        Self { source_hash }
120    }
121
122    /// Hashes a retained value with its exact sealed host type.
123    pub fn stored_value_hash<Type>(&self, value: &HostStoredValue<Type>) -> u64 {
124        (self.source_hash)(&value.value)
125    }
126
127    /// Hashes an existentially retained value and its specialized shape.
128    pub fn dynamic_value_hash(&self, value: &HostStoredDynamic) -> u64 {
129        (self.source_hash)(value.runtime_value())
130    }
131}
132
133impl<'context> HostExternalInspection<'context> {
134    pub(crate) fn new(
135        inspect: &'context dyn Fn(&crate::runtime::StoredRuntimeValue) -> EcoString,
136    ) -> Self {
137        Self { inspect }
138    }
139
140    /// Inspects a retained value with its exact sealed host type.
141    pub fn inspect_stored_value<Type>(&self, value: &HostStoredValue<Type>) -> EcoString {
142        (self.inspect)(&value.value)
143    }
144
145    /// Inspects an existentially retained value using its specialized shape.
146    pub fn inspect_dynamic_value(&self, value: &HostStoredDynamic) -> EcoString {
147        (self.inspect)(value.runtime_value())
148    }
149}
150
151/// The source-facing identity of one registered external type.
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct HostExternalTypeSchema {
154    package: EcoString,
155    module: EcoString,
156    name: EcoString,
157    parameter_count: usize,
158}
159
160impl HostExternalTypeSchema {
161    pub fn of<Schema: HostExternalSchema>() -> Self {
162        Self::new(
163            Schema::PACKAGE,
164            Schema::MODULE,
165            Schema::NAME,
166            Schema::PARAMETER_COUNT,
167        )
168    }
169
170    pub fn new(
171        package: impl Into<EcoString>,
172        module: impl Into<EcoString>,
173        name: impl Into<EcoString>,
174        parameter_count: usize,
175    ) -> Self {
176        Self {
177            package: package.into(),
178            module: module.into(),
179            name: name.into(),
180            parameter_count,
181        }
182    }
183
184    pub fn package(&self) -> &EcoString {
185        &self.package
186    }
187
188    pub fn module(&self) -> &EcoString {
189        &self.module
190    }
191
192    pub fn name(&self) -> &EcoString {
193        &self.name
194    }
195
196    pub fn parameter_count(&self) -> usize {
197        self.parameter_count
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::{
204        HostExternalEquality, HostExternalHashing, HostExternalInspection, HostStoredDynamic,
205        HostStoredValue,
206    };
207    use num_bigint::BigInt;
208
209    #[test]
210    fn external_semantics_contexts_delegate_typed_and_dynamic_values() {
211        let typed = HostStoredValue::<BigInt>::new(crate::runtime::StoredRuntimeValue::test_int(
212            BigInt::from(7),
213        ));
214        let other = HostStoredValue::<BigInt>::new(crate::runtime::StoredRuntimeValue::test_int(
215            BigInt::from(8),
216        ));
217        let dynamic = HostStoredDynamic::new(crate::runtime::StoredRuntimeValue::test_int(
218            BigInt::from(9),
219        ));
220        let other_dynamic = HostStoredDynamic::new(crate::runtime::StoredRuntimeValue::test_int(
221            BigInt::from(10),
222        ));
223        let equal =
224            |_: &crate::runtime::StoredRuntimeValue, _: &crate::runtime::StoredRuntimeValue| true;
225        let source_hash = |_: &crate::runtime::StoredRuntimeValue| 17;
226        let inspect = |_: &crate::runtime::StoredRuntimeValue| "Int".into();
227        let equality = HostExternalEquality::new(&equal);
228        let hashing = HostExternalHashing::new(&source_hash);
229        let inspection = HostExternalInspection::new(&inspect);
230
231        assert!(equality.stored_values_equal(&typed, &other));
232        assert!(equality.dynamic_values_equal(&dynamic, &other_dynamic));
233        assert_eq!(hashing.stored_value_hash(&typed), 17);
234        assert_eq!(hashing.dynamic_value_hash(&dynamic), 17);
235        assert_eq!(inspection.inspect_stored_value(&typed), "Int");
236        assert_eq!(inspection.inspect_dynamic_value(&dynamic), "Int");
237    }
238}
239
240#[cfg(test)]
241pub(crate) struct ExternalTestProfile;
242
243#[cfg(test)]
244#[derive(Default)]
245pub(crate) struct ExternalTestRunState {
246    pub(crate) provider: (),
247}
248
249#[cfg(test)]
250#[derive(Default)]
251pub(crate) struct ExternalTestStores {
252    pub(crate) units: HostExternalStore<()>,
253    pub(crate) integers: HostExternalStore<num_bigint::BigInt>,
254    pub(crate) indices: HostExternalStore<usize>,
255}
256
257#[cfg(test)]
258impl HostProfile for ExternalTestProfile {
259    type RunState = ExternalTestRunState;
260    type ExternalStores = ExternalTestStores;
261}