Skip to main content

turbomcp_core/
marker.rs

1//! Platform-adaptive marker traits for cross-platform compatibility.
2//!
3//! These traits enable unified handler definitions that work on both native and WASM targets.
4//! On native targets, the traits require `Send`/`Sync` bounds for multi-threaded executors.
5//! On WASM targets, the traits have no bounds since WASM is single-threaded and `JsValue` is `!Send`.
6//!
7//! # Background
8//!
9//! The fundamental challenge for unified native/WASM code is that [`wasm_bindgen::JsValue`]
10//! is `!Send` by design due to JavaScript's slab-based object management. This has
11//! "huge trickle-down effects" on downstream crates.
12//!
13//! The solution is conditional compilation with marker traits that adapt to the target.
14//!
15//! # Example
16//!
17//! ```rust,ignore
18//! use turbomcp_core::{MaybeSend, MaybeSync};
19//!
20//! // This trait works on both native (with Send) and WASM (without Send)
21//! trait MyHandler: MaybeSend + MaybeSync {
22//!     fn handle(&self) -> impl Future<Output = ()> + MaybeSend;
23//! }
24//! ```
25//!
26//! # Platform Behavior
27//!
28//! - **Native (`x86_64`, `aarch64`, etc.)**: `MaybeSend` requires `Send`, `MaybeSync` requires `Sync`
29//! - **WASM (`wasm32`)**: Both traits are blanket-implemented for all types (no bounds)
30
31/// Marker trait that requires `Send` on native targets, nothing on WASM.
32///
33/// This enables unified trait definitions that work on both platforms:
34/// - On native: futures must be `Send` for multi-threaded executors like tokio
35/// - On WASM: no `Send` bound needed since WASM is single-threaded
36///
37/// # Example
38///
39/// ```rust,ignore
40/// use turbomcp_core::MaybeSend;
41/// use std::future::Future;
42///
43/// trait MyAsyncTrait {
44///     fn do_work(&self) -> impl Future<Output = ()> + MaybeSend;
45/// }
46/// ```
47#[cfg(not(target_arch = "wasm32"))]
48pub trait MaybeSend: Send {}
49
50#[cfg(not(target_arch = "wasm32"))]
51impl<T: Send + ?Sized> MaybeSend for T {}
52
53/// Marker trait with no bounds on WASM (single-threaded).
54///
55/// On WASM targets, `MaybeSend` is blanket-implemented for all types since
56/// WASM is single-threaded and doesn't require `Send` bounds.
57#[cfg(target_arch = "wasm32")]
58pub trait MaybeSend {}
59
60#[cfg(target_arch = "wasm32")]
61impl<T: ?Sized> MaybeSend for T {}
62
63/// Marker trait that requires `Sync` on native targets, nothing on WASM.
64///
65/// This enables unified trait definitions that work on both platforms:
66/// - On native: handlers must be `Sync` for shared access across threads
67/// - On WASM: no `Sync` bound needed since WASM is single-threaded
68///
69/// # Example
70///
71/// ```rust,ignore
72/// use turbomcp_core::{MaybeSend, MaybeSync};
73///
74/// trait MyHandler: Clone + MaybeSend + MaybeSync + 'static {
75///     fn handle(&self);
76/// }
77/// ```
78#[cfg(not(target_arch = "wasm32"))]
79pub trait MaybeSync: Sync {}
80
81#[cfg(not(target_arch = "wasm32"))]
82impl<T: Sync + ?Sized> MaybeSync for T {}
83
84/// Marker trait with no bounds on WASM (single-threaded).
85///
86/// On WASM targets, `MaybeSync` is blanket-implemented for all types since
87/// WASM is single-threaded and doesn't require `Sync` bounds.
88#[cfg(target_arch = "wasm32")]
89pub trait MaybeSync {}
90
91#[cfg(target_arch = "wasm32")]
92impl<T: ?Sized> MaybeSync for T {}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_string_is_maybe_send() {
100        fn assert_maybe_send<T: MaybeSend>() {}
101        assert_maybe_send::<String>();
102    }
103
104    #[test]
105    fn test_string_is_maybe_sync() {
106        fn assert_maybe_sync<T: MaybeSync>() {}
107        assert_maybe_sync::<String>();
108    }
109
110    #[test]
111    fn test_arc_is_maybe_send_sync() {
112        fn assert_bounds<T: MaybeSend + MaybeSync>() {}
113        assert_bounds::<std::sync::Arc<String>>();
114    }
115
116    // On native, verify that non-Send types don't implement MaybeSend
117    #[cfg(not(target_arch = "wasm32"))]
118    #[test]
119    fn test_rc_is_not_maybe_send() {
120        // This test verifies at compile time that Rc<T> doesn't implement MaybeSend on native
121        // We can't easily write a negative test, but we can verify the marker trait exists
122        fn _needs_send<T: Send>() {}
123        fn _needs_maybe_send<T: MaybeSend>() {}
124
125        // Rc<String> should NOT compile with _needs_send or _needs_maybe_send on native
126        // This is a compile-time guarantee, not a runtime test
127    }
128}