juncture_core/wasm_send.rs
1//! WASM-compatible `Send` wrappers.
2//!
3//! On WASM (`wasm32-unknown-unknown`), many types (e.g., `reqwest::Response`)
4//! are `!Send` because they contain `Rc<RefCell<...>>` internally. However,
5//! WASM is single-threaded, so `Send` is trivially satisfied at runtime.
6//!
7//! This module provides wrappers that make `!Send` types and futures `Send`
8//! on WASM. On native targets, these are no-ops.
9//!
10//! # Safety
11//!
12//! This is sound on WASM because:
13//! - WASM is single-threaded (no actual thread safety concerns)
14//! - `Send` is only a compile-time check
15//! - The wrappers are never used to actually send data across threads
16
17/// Wrapper that makes any type `Send` on WASM.
18///
19/// On native targets, `WasmSend<T>` is `Send` only when `T: Send`.
20/// On WASM, `WasmSend<T>` is always `Send` (single-threaded).
21#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
22pub struct WasmSend<T>(pub T);
23
24// SAFETY: On WASM (single-threaded), Send is trivially satisfied.
25#[cfg(target_family = "wasm")]
26unsafe impl<T> Send for WasmSend<T> {}
27
28impl<T> WasmSend<T> {
29 /// Create a new wrapper.
30 pub const fn new(value: T) -> Self {
31 Self(value)
32 }
33
34 /// Unwrap the inner value.
35 pub fn into_inner(self) -> T {
36 self.0
37 }
38
39 /// Get a reference to the inner value.
40 pub const fn inner(&self) -> &T {
41 &self.0
42 }
43
44 /// Get a mutable reference to the inner value.
45 #[allow(
46 clippy::missing_const_for_fn,
47 reason = "&mut self in const fn is unstable"
48 )]
49 pub fn inner_mut(&mut self) -> &mut T {
50 &mut self.0
51 }
52}
53
54impl<T> std::ops::Deref for WasmSend<T> {
55 type Target = T;
56
57 fn deref(&self) -> &Self::Target {
58 &self.0
59 }
60}
61
62impl<T> std::ops::DerefMut for WasmSend<T> {
63 fn deref_mut(&mut self) -> &mut Self::Target {
64 &mut self.0
65 }
66}
67
68/// Wrap a future to be `Send` on WASM.
69///
70/// On WASM, wraps the future in a `Send` wrapper (single-threaded safety).
71/// On native, returns the future as-is (must already be `Send`).
72///
73/// # Safety
74///
75/// On WASM, this is sound because WASM is single-threaded.
76/// On native, the future must already be `Send`.
77#[cfg(target_family = "wasm")]
78pub fn force_send<F: std::future::Future>(
79 future: F,
80) -> impl std::future::Future<Output = F::Output> + Send {
81 ForceSend(future)
82}
83
84/// On native, just return the future (must already be `Send`).
85#[cfg(not(target_family = "wasm"))]
86pub fn force_send<F: std::future::Future + Send>(
87 future: F,
88) -> impl std::future::Future<Output = F::Output> + Send {
89 future
90}
91
92/// Wrapper that makes any future `Send` on WASM.
93///
94/// # Safety
95///
96/// On WASM (single-threaded), `Send` is trivially satisfied.
97/// The future is never actually sent across threads.
98#[cfg(target_family = "wasm")]
99struct ForceSend<F>(F);
100
101#[cfg(target_family = "wasm")]
102// SAFETY: On WASM (single-threaded), Send is trivially satisfied.
103unsafe impl<F> Send for ForceSend<F> {}
104
105#[cfg(target_family = "wasm")]
106impl<F: std::future::Future> std::future::Future for ForceSend<F> {
107 type Output = F::Output;
108
109 fn poll(
110 self: std::pin::Pin<&mut Self>,
111 cx: &mut std::task::Context<'_>,
112 ) -> std::task::Poll<Self::Output> {
113 // SAFETY: ForceSend is a structural pin wrapper.
114 // We never move the inner future.
115 unsafe { self.map_unchecked_mut(|s| &mut s.0).poll(cx) }
116 }
117}
118
119/// Wrap a stream to be `Send` on WASM.
120///
121/// On WASM, wraps the stream in a `Send` wrapper (single-threaded safety).
122/// On native, returns the stream as-is (must already be `Send`).
123#[cfg(target_family = "wasm")]
124pub fn force_send_stream<S: futures::Stream>(
125 stream: S,
126) -> impl futures::Stream<Item = S::Item> + Send {
127 ForceSendStream(stream)
128}
129
130/// On native, just return the stream (must already be `Send`).
131#[cfg(not(target_family = "wasm"))]
132pub fn force_send_stream<S: futures::Stream + Send>(
133 stream: S,
134) -> impl futures::Stream<Item = S::Item> + Send {
135 stream
136}
137
138/// Wrapper that makes any stream `Send` on WASM.
139#[cfg(target_family = "wasm")]
140struct ForceSendStream<S>(S);
141
142#[cfg(target_family = "wasm")]
143// SAFETY: On WASM (single-threaded), Send is trivially satisfied.
144unsafe impl<S> Send for ForceSendStream<S> {}
145
146#[cfg(target_family = "wasm")]
147impl<S: futures::Stream> futures::Stream for ForceSendStream<S> {
148 type Item = S::Item;
149
150 fn poll_next(
151 self: std::pin::Pin<&mut Self>,
152 cx: &mut std::task::Context<'_>,
153 ) -> std::task::Poll<Option<Self::Item>> {
154 // SAFETY: ForceSendStream is a structural pin wrapper.
155 unsafe { self.map_unchecked_mut(|s| &mut s.0).poll_next(cx) }
156 }
157}