Skip to main content

wasm_link/
interface.rs

1use std::sync::{ Arc, Mutex };
2use std::collections::{ HashMap, HashSet };
3use wasmtime::component::{ Linker, ResourceType, Val };
4
5use crate::{ Binding, PluginContext };
6use crate::cardinality::Cardinality ;
7use crate::linker::{ dispatch_all, dispatch_method };
8use crate::resource_wrapper::ResourceWrapper ;
9
10/// A single WIT interface within a [`Binding`].
11///
12/// Each interface declares functions and resources that implementers must export.
13/// Note that the interface name is not a part of the struct but rather a key in
14/// a hash map provided to the Binding constructor. This is to prevent duplicate
15/// interface names.
16///
17/// ```
18/// # use std::collections::{ HashMap, HashSet };
19/// # use wasm_link::{ Binding, Interface, PluginContext, PluginInstance, ResourceTable };
20/// # use wasm_link::cardinality::AtMostOne ;
21/// # struct Ctx { resource_table: ResourceTable }
22/// # impl PluginContext for Ctx {
23/// # 	fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
24/// # }
25/// let binding: Binding<String, Ctx, AtMostOne<String, PluginInstance<Ctx>>> = Binding::new(
26/// 	"my:package",
27/// 	HashMap::from([
28/// 		( "interface-a".to_string(), Interface::new( HashMap::new(), HashSet::new() )),
29/// 		( "interface-b".to_string(), Interface::new( HashMap::new(), HashSet::new() )),
30/// 	]),
31/// 	AtMostOne( None ),
32/// );
33/// # let _ = binding;
34/// ```
35#[derive( Debug, Clone, Default )]
36pub struct Interface {
37	/// Functions exported by this interface
38	functions: HashMap<String, Function>,
39	/// Resource types defined by this interface
40	resources: HashSet<String>,
41}
42
43impl Interface {
44	/// Creates a new interface declaration.
45	pub fn new(
46		functions: HashMap<String, Function>,
47		resources: HashSet<String>,
48	) -> Self {
49		Self { functions, resources }
50	}
51
52	#[inline]
53	pub(crate) fn function( &self, name: &str ) -> Option<&Function> {
54		self.functions.get( name )
55	}
56
57	#[inline]
58	pub(crate) fn add_to_linker<PluginId, Ctx, Plugins>(
59		&self,
60		linker: &mut Linker<Ctx>,
61		package_name: &str,
62		interface_ident: &str,
63		interface_name: &str,
64		binding: &Binding<PluginId, Ctx, Plugins>,
65	) -> Result<(), wasmtime::Error>
66	where
67		PluginId: std::hash::Hash + Eq + Clone + Send + Sync + Into<Val> + 'static,
68		Ctx: PluginContext,
69		Plugins: Cardinality<PluginId, crate::PluginInstance<Ctx>> + 'static,
70		<Plugins as Cardinality<PluginId, crate::PluginInstance<Ctx>>>::Rebind<Mutex<crate::PluginInstance<Ctx>>>: Send + Sync,
71		<Plugins as Cardinality<PluginId, crate::PluginInstance<Ctx>>>::Rebind<Mutex<crate::PluginInstance<Ctx>>>: Cardinality<PluginId, Mutex<crate::PluginInstance<Ctx>>>,
72		<<Plugins as Cardinality<PluginId, crate::PluginInstance<Ctx>>>::Rebind<Mutex<crate::PluginInstance<Ctx>>> as Cardinality<PluginId, Mutex<crate::PluginInstance<Ctx>>>>::Rebind<Val>: Into<Val>,
73	{
74		let mut linker_root = linker.root();
75		let mut linker_instance = linker_root.instance( interface_ident )?;
76
77		self.functions.iter().try_for_each(|( name, metadata )| {
78
79			let package_name_clone = package_name.to_string();
80			let interface_name_clone = interface_name.to_string();
81			let binding_clone = binding.clone();
82			let name_clone = name.clone();
83			let metadata_clone = metadata.clone();
84
85			macro_rules! link {( $dispatch: expr ) => {
86				linker_instance.func_new( name, move | ctx, _ty, args, results | Ok(
87					results[0] = $dispatch( &binding_clone, ctx, &package_name_clone, &interface_name_clone, &name_clone, &metadata_clone, args )
88				))
89			}}
90
91			match metadata.kind() {
92				FunctionKind::Freestanding => link!( dispatch_all ),
93				FunctionKind::Method => link!( dispatch_method ),
94			}
95
96		})?;
97
98		self.resources.iter().try_for_each(| resource | linker_instance
99			.resource( resource.as_str(), ResourceType::host::<Arc<ResourceWrapper<PluginId>>>(), ResourceWrapper::<PluginId>::drop )
100		)?;
101
102		Ok(())
103
104	}
105
106}
107
108/// Denotes whether a function is freestanding or a resource method.
109/// Constructors are treated as freestanding functions.
110///
111/// Determines how dispatch is routed during cross-plugin calls:
112/// freestanding functions broadcast to all plugins, while methods
113/// route to the specific plugin that owns the resource.
114#[derive( Debug, Clone, Copy, Eq, PartialEq )]
115pub enum FunctionKind {
116	/// A freestanding function — dispatched to all plugins.
117	Freestanding,
118	/// A resource method (has a `self` parameter) — routed to the plugin that owns the resource.
119	Method,
120}
121
122/// Metadata about a function declared by an interface.
123///
124/// Provides information needed during linking to wire up cross-plugin dispatch.
125#[derive( Debug, Clone )]
126pub struct Function {
127	/// Whether this function is freestanding or a resource method.
128	kind: FunctionKind,
129	/// The function's return kind for dispatch handling
130	return_kind: ReturnKind,
131}
132
133impl Function {
134	/// Creates a new function metadata entry.
135	pub fn new(
136		kind: FunctionKind,
137		return_kind: ReturnKind,
138	) -> Self {
139		Self { kind, return_kind }
140	}
141
142	/// The function's return kind for dispatch handling.
143	pub fn return_kind( &self ) -> ReturnKind { self.return_kind }
144
145	/// Whether this function is freestanding or a resource method.
146	pub fn kind( &self ) -> FunctionKind { self.kind }
147
148}
149
150/// Categorizes a function's return for dispatch handling.
151///
152/// Determines how return values are processed during cross-plugin dispatch.
153/// Resources require special wrapping to track ownership across plugin
154/// boundaries, while plain data can be passed through directly.
155///
156/// # Choosing the Right Variant
157///
158/// **When uncertain, use [`MayContainResources`]( Self::MayContainResources ).**
159/// Using [`AssumeNoResources`]( Self::AssumeNoResources ) when resources are
160/// actually present will cause resource handles to be passed through unwrapped
161/// causing runtime exceptions.
162///
163/// [`AssumeNoResources`]( Self::AssumeNoResources ) is a performance optimization
164/// that skips the wrapping step. Only use it when you are certain the return type
165/// contains no resource handles anywhere in its structure (including nested within
166/// records, variants, lists, etc.).
167#[derive( Copy, Clone, Eq, PartialEq, Hash, Debug, Default )]
168pub enum ReturnKind {
169	/// Function returns nothing (void).
170	#[default] Void,
171	/// Function may return resource handles - always wraps safely.
172	///
173	/// Use this variant whenever resources might be present in the return value,
174	/// or when you're unsure. The performance overhead of wrapping is preferable
175	/// to the undefined behavior caused by unwrapped resource handles.
176	MayContainResources,
177	/// Assumes no resource handles are present - skips wrapping for performance.
178	///
179	/// **Warning:** Only use this if you are certain no resources are present.
180	/// If resources are returned but this variant is used, resource handles will
181	/// not be wrapped correctly, potentially causing undefined behavior in plugins.
182	/// When in doubt, use [`MayContainResources`](Self::MayContainResources) instead.
183	AssumeNoResources,
184}
185
186impl std::fmt::Display for ReturnKind {
187	fn fmt( &self, f: &mut std::fmt::Formatter ) -> Result<(), std::fmt::Error> {
188		match self {
189			Self::Void => write!( f, "Function returns no data" ),
190			Self::MayContainResources => write!( f, "Return type may contain resources" ),
191			Self::AssumeNoResources => write!( f, "Function is assumed to not return any resources" ),
192		}
193	}
194}