kithara_platform/maybe_send.rs
1#[cfg(not(target_arch = "wasm32"))]
2pub trait MaybeSend: Send {}
3#[cfg(not(target_arch = "wasm32"))]
4impl<T: Send> MaybeSend for T {}
5
6#[cfg(target_arch = "wasm32")]
7pub trait MaybeSend {}
8#[cfg(target_arch = "wasm32")]
9impl<T> MaybeSend for T {}
10
11#[cfg(not(target_arch = "wasm32"))]
12pub trait MaybeSync: Sync {}
13#[cfg(not(target_arch = "wasm32"))]
14impl<T: Sync> MaybeSync for T {}
15
16#[cfg(target_arch = "wasm32")]
17pub trait MaybeSync {}
18#[cfg(target_arch = "wasm32")]
19impl<T> MaybeSync for T {}
20
21/// Trait alias for `Future + MaybeSend`.
22///
23/// On native: `Future + Send` — allows `tokio::spawn`.
24/// On WASM: just `Future` — no `Send` requirement.
25///
26/// Use in return-position impl trait in trait methods:
27/// ```ignore
28/// fn poll_demand(&mut self) -> impl MaybeSendFuture<Output = Option<Plan>>;
29/// ```
30#[cfg(not(target_arch = "wasm32"))]
31pub trait MaybeSendFuture: Future + Send {}
32#[cfg(not(target_arch = "wasm32"))]
33impl<T: Future + Send> MaybeSendFuture for T {}
34
35#[cfg(target_arch = "wasm32")]
36pub trait MaybeSendFuture: Future {}
37#[cfg(target_arch = "wasm32")]
38impl<T: Future> MaybeSendFuture for T {}
39
40/// Boxed future that is `Send` on native, unrestricted on WASM.
41///
42/// Use as return type in trait methods to make the returned future
43/// `Send`-compatible on native (enabling `tokio::spawn`) while keeping
44/// WASM compatibility.
45///
46/// ```ignore
47/// fn plan(&mut self) -> BoxFuture<'_, PlanOutcome<Self::Plan>>;
48/// ```
49#[cfg(not(target_arch = "wasm32"))]
50pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + Send + 'a>>;
51
52#[cfg(target_arch = "wasm32")]
53pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + 'a>>;
54
55/// Wrapper that unconditionally implements `Send` on WASM.
56///
57/// On native, `WasmSend<T>` is `Send` only if `T: Send` (no magic).
58/// On WASM, `WasmSend<T>` is always `Send`, allowing `!Send` types
59/// (like `Writer` wrapping a `JsValue`-backed response stream) to be
60/// moved into a Web Worker.
61///
62/// # Safety contract
63///
64/// The caller must ensure that:
65/// - The wrapped value is **not** actively used across threads simultaneously.
66/// - When transferring to a Web Worker, any `!Send` JS-backed values inside
67/// must be `None`/uninitialized at transfer time and only populated inside
68/// the target worker (where JS objects belong to the local context).
69///
70/// `FileDownloader` satisfies this: `writer` is `None` when moved to the
71/// Worker, and `ensure_writer()` creates the HTTP stream inside the Worker.
72pub struct WasmSend<T>(T);
73
74impl<T> WasmSend<T> {
75 /// Wrap a value.
76 pub fn new(value: T) -> Self {
77 Self(value)
78 }
79
80 /// Immutable access.
81 pub fn get(&self) -> &T {
82 &self.0
83 }
84
85 /// Mutable access.
86 pub fn get_mut(&mut self) -> &mut T {
87 &mut self.0
88 }
89}
90
91#[cfg(not(target_arch = "wasm32"))]
92// SAFETY: delegates to T's Send impl — no additional invariants.
93unsafe impl<T: Send> Send for WasmSend<T> {}
94
95#[cfg(target_arch = "wasm32")]
96// SAFETY: see struct-level safety contract. The wrapped value must not be
97unsafe impl<T> Send for WasmSend<T> {}