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}