linked/
box.rs

1// Copyright (c) Microsoft Corporation.
2// Copyright (c) Folo authors.
3
4use std::boxed::Box as StdBox;
5use std::ops::{Deref, DerefMut};
6
7/// A linked object that acts like a `std::boxed::Box<dyn MyTrait>`.
8///
9/// Intended to represent linked instances of `T` where `T: MyTrait`. This is for use with types
10/// that are always exposed to user code as trait objects via `linked::Box<dyn MyTrait>`.
11///
12/// The `Box` itself implements the linked object API from [`#[linked::object]`][3]. The type
13/// `T` is not decorated with the [`#[linked::object]`][3] attribute when using `Box` and simply
14/// uses linked object patterns in its constructor.
15///
16/// # Usage
17///
18/// Use it like a regular `std::boxed::Box<T>` that also happens to support the linked object
19/// patterns. The box can be used via all the standard mechanisms such as:
20///
21/// * [`linked::instances!`][1]
22/// * [`linked::thread_local_rc!`][2]
23/// * [`linked::thread_local_arc!`][4] (if `T: Sync`)
24/// * [`linked::InstancePerThread<T>`][5]
25/// * [`linked::InstancePerThreadSync<T>`][6] (if `T: Sync`)
26/// * [`linked::Family<T>`][7]
27///
28/// # Implementation
29///
30/// Instead of a typical constructor, create one that returns `linked::Box<dyn MyTrait>`. Inside
31/// this constructor, create a `linked::Box` instance using the [`linked::new_box!` macro][8].
32/// The first macro parameter is the type of the trait object inside the box, and the second is a
33/// `Self` struct-expression to create one linked instance of the implementation type.
34///
35/// ```
36/// # trait ConfigSource {}
37/// # impl ConfigSource for XmlConfig {}
38/// // If using linked::Box, do not put `#[linked::object]` on the struct.
39/// // The linked::Box itself is the linked object and our struct is only its contents.
40/// struct XmlConfig {
41///     config: String,
42/// }
43///
44/// impl XmlConfig {
45///     pub fn new_as_config_source() -> linked::Box<dyn ConfigSource> {
46///         // Constructing instances works logically the same as for regular linked objects.
47///         //
48///         // The only differences are:
49///         // 1. We use `linked::new_box!` instead of `linked::new!`
50///         // 2. There is an additional parameter to the macro to name the trait object type
51///         linked::new_box!(
52///             dyn ConfigSource,
53///             Self {
54///                 config: "xml".to_string(),
55///             }
56///         )
57///     }
58/// }
59/// ```
60///
61/// Any connections between the instances should be established via the captured state of this
62/// closure (e.g. sharing an `Arc` or setting up messaging channels).
63///
64/// # Example
65///
66/// Using the linked objects as `linked::Box<dyn ConfigSource>`, without the user code knowing the
67/// exact type of the object in the box:
68///
69/// ```
70/// trait ConfigSource {
71///     fn config(&self) -> String;
72/// }
73///
74/// struct XmlConfig {}
75/// struct IniConfig {}
76///
77/// impl ConfigSource for XmlConfig {
78///     fn config(&self) -> String {
79///         "xml".to_string()
80///     }
81/// }
82///
83/// impl ConfigSource for IniConfig {
84///     fn config(&self) -> String {
85///         "ini".to_string()
86///     }
87/// }
88///
89/// impl XmlConfig {
90///     pub fn new_as_config_source() -> linked::Box<dyn ConfigSource> {
91///         linked::new_box!(dyn ConfigSource, XmlConfig {})
92///     }
93/// }
94///
95/// impl IniConfig {
96///     pub fn new_as_config_source() -> linked::Box<dyn ConfigSource> {
97///         linked::new_box!(dyn ConfigSource, IniConfig {})
98///     }
99/// }
100///
101/// let xml_config = XmlConfig::new_as_config_source();
102/// let ini_config = IniConfig::new_as_config_source();
103///
104/// let configs: [linked::Box<dyn ConfigSource>; 2] = [xml_config, ini_config];
105///
106/// assert_eq!(configs[0].config(), "xml".to_string());
107/// assert_eq!(configs[1].config(), "ini".to_string());
108/// ```
109///
110/// [1]: crate::instances
111/// [2]: crate::thread_local_rc
112/// [3]: crate::object
113/// [4]: crate::thread_local_arc
114/// [5]: crate::InstancePerThread
115/// [6]: crate::InstancePerThreadSync
116/// [7]: crate::Family
117/// [8]: crate::new_box
118#[linked::object]
119#[derive(Debug)]
120pub struct Box<T: ?Sized + 'static> {
121    value: StdBox<T>,
122}
123
124impl<T: ?Sized> Box<T> {
125    /// This is an implementation detail of the `linked::new_box!` macro and is not part of the
126    /// public API. It is not meant to be used directly and may change or be removed at any time.
127    #[doc(hidden)]
128    #[must_use]
129    pub fn new(instance_factory: impl Fn() -> StdBox<T> + Send + Sync + 'static) -> Self {
130        linked::new!(Self {
131            value: (instance_factory)(),
132        })
133    }
134}
135
136impl<T: ?Sized + 'static> Deref for Box<T> {
137    type Target = T;
138
139    #[inline(always)]
140    fn deref(&self) -> &Self::Target {
141        &self.value
142    }
143}
144
145impl<T: ?Sized + 'static> DerefMut for Box<T> {
146    #[inline(always)]
147    fn deref_mut(&mut self) -> &mut Self::Target {
148        &mut self.value
149    }
150}
151
152/// Defines the template used to create every instance in a `linked::Box<T>` object family.
153///
154/// This macro is meant to be used in the context of creating a new linked instance of
155/// `T` that is meant to be always expressed via an abstraction (`dyn SomeTrait`).
156///
157/// # Arguments
158///
159/// * `$dyn_trait` - The trait object that the linked object is to be used as (e.g. `dyn SomeTrait`).
160/// * `$ctor` - The Self-expression that serves as the template for constructing new linked
161///   instances on demand. This will move-capture any referenced state. All captured
162///   values must be thread-safe (`Send` + `Sync` + `'static`).
163///
164/// # Example
165///
166/// ```rust
167/// # trait ConfigSource {}
168/// # impl ConfigSource for XmlConfig {}
169/// // If using linked::Box, do not put `#[linked::object]` on the struct.
170/// // The linked::Box itself is the linked object and our struct is only its contents.
171/// struct XmlConfig {
172///     config: String,
173/// }
174///
175/// impl XmlConfig {
176///     pub fn new_as_config_source() -> linked::Box<dyn ConfigSource> {
177///         linked::new_box!(
178///             dyn ConfigSource,
179///             Self {
180///                 config: "xml".to_string(),
181///             }
182///         )
183///     }
184/// }
185/// ```
186///
187/// See `examples/linked_box.rs` for a complete example.
188#[macro_export]
189macro_rules! new_box {
190    ($dyn_trait:ty, $ctor:expr) => {
191        #[expect(trivial_casts, reason = "clearly expresses intent")]
192        ::linked::Box::new(move || ::std::boxed::Box::new($ctor) as ::std::boxed::Box<$dyn_trait>)
193    };
194}
195
196#[cfg(test)]
197#[cfg_attr(coverage_nightly, coverage(off))]
198mod tests {
199    use std::thread;
200
201    use crate::Object;
202
203    #[test]
204    fn linked_box() {
205        trait ConfigSource {
206            fn config(&self) -> String;
207            fn set_config(&mut self, config: String);
208        }
209
210        struct XmlConfig {
211            config: String,
212        }
213        struct IniConfig {
214            config: String,
215        }
216
217        impl ConfigSource for XmlConfig {
218            fn config(&self) -> String {
219                self.config.clone()
220            }
221
222            fn set_config(&mut self, config: String) {
223                self.config = config;
224            }
225        }
226
227        impl ConfigSource for IniConfig {
228            fn config(&self) -> String {
229                self.config.clone()
230            }
231
232            fn set_config(&mut self, config: String) {
233                self.config = config;
234            }
235        }
236
237        impl XmlConfig {
238            fn new_as_config_source() -> linked::Box<dyn ConfigSource> {
239                linked::new_box!(
240                    dyn ConfigSource,
241                    Self {
242                        config: "xml".to_string(),
243                    }
244                )
245            }
246        }
247
248        impl IniConfig {
249            fn new_as_config_source() -> linked::Box<dyn ConfigSource> {
250                linked::new_box!(
251                    dyn ConfigSource,
252                    Self {
253                        config: "ini".to_string(),
254                    }
255                )
256            }
257        }
258
259        let xml_config = XmlConfig::new_as_config_source();
260        let ini_config = IniConfig::new_as_config_source();
261
262        let mut configs = [xml_config, ini_config];
263
264        assert_eq!(configs[0].config(), "xml".to_string());
265        assert_eq!(configs[1].config(), "ini".to_string());
266
267        configs[0].set_config("xml2".to_string());
268        configs[1].set_config("ini2".to_string());
269
270        assert_eq!(configs[0].config(), "xml2".to_string());
271        assert_eq!(configs[1].config(), "ini2".to_string());
272
273        let xml_config_family = configs[0].family();
274        let ini_config_family = configs[1].family();
275
276        thread::spawn(move || {
277            let xml_config: linked::Box<dyn ConfigSource> = xml_config_family.into();
278            let ini_config: linked::Box<dyn ConfigSource> = ini_config_family.into();
279
280            // Note that the "config" is local to each instance, so it reset to the initial value.
281            assert_eq!(xml_config.config(), "xml".to_string());
282            assert_eq!(ini_config.config(), "ini".to_string());
283        })
284        .join()
285        .unwrap();
286    }
287}