dyn_utils/lib.rs
1//! A utility library for working with [trait objects].
2//!
3//! Trait objects (i.e. `dyn Trait`) are unsized and therefore need to be stored in a container
4//! such as `Box`. This crate provides [`DynObject`], a container for trait objects with a
5//! generic [`storage`].
6//!
7//! [`storage::Raw`] stores objects in place, making `DynObject<dyn Trait, storage::Raw>`
8//! allocation-free. On the other hand, [`storage::RawOrBox`] falls back to an allocated `Box` if
9//! the object is too large to fit in place.
10//!
11//! Avoiding one allocation makes `DynObject` a good alternative to `Box` when writing a
12//! [dyn-compatible] version of a trait with return-position `impl Trait`, such as async methods.
13//!
14//! # Examples
15//!
16//! ```rust
17//! use dyn_utils::object::DynObject;
18//!
19//! trait Callback {
20//! fn call(&self, arg: &str) -> impl Future<Output = ()> + Send;
21//! }
22//!
23//! // Dyn-compatible version
24//! trait DynCallback {
25//! fn call<'a>(&'a self, arg: &'a str) -> DynObject<dyn Future<Output = ()> + Send + 'a>;
26//! }
27//!
28//! impl<T: Callback> DynCallback for T {
29//! fn call<'a>(&'a self, arg: &'a str) -> DynObject<dyn Future<Output = ()> + Send + 'a> {
30//! DynObject::new(self.call(arg))
31//! }
32//! }
33//!
34//! async fn exec_callback(callback: &dyn DynCallback) {
35//! callback.call("Hello world!").await;
36//! }
37//! ```
38//!
39//! This crate also provides [`dyn_trait`] proc-macro to achieve the same result as above:
40//!
41//! ```rust
42//! # #[cfg(feature = "macros")]
43//! #[dyn_utils::dyn_trait] // generates `DynCallback` trait
44//! trait Callback {
45//! fn call(&self, arg: &str) -> impl Future<Output = ()> + Send;
46//! }
47//!
48//! # #[cfg(feature = "macros")]
49//! async fn exec_callback(callback: &dyn DynCallback) {
50//! callback.call("Hello world!").await;
51//! }
52//! ```
53//!
54//! [trait objects]: https://doc.rust-lang.org/std/keyword.dyn.html
55//! [dyn-compatible]: https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility
56#![cfg_attr(docsrs, feature(doc_cfg))]
57#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
58#![no_std]
59#![forbid(missing_docs)]
60
61#[cfg(any(feature = "alloc", doc))]
62extern crate alloc;
63
64use core::{
65 hint, mem,
66 pin::Pin,
67 task::{Context, Poll},
68};
69
70mod impls;
71#[cfg(feature = "macros")]
72mod macros;
73pub mod object;
74pub mod storage;
75
76#[cfg(feature = "macros")]
77pub use macros::{dyn_object, dyn_trait, sync};
78pub use object::DynObject;
79#[cfg(all(doc, not(feature = "macros")))]
80#[doc(hidden)]
81pub fn dyn_trait() {}
82#[cfg(all(doc, not(feature = "macros")))]
83#[doc(hidden)]
84pub fn dyn_object() {}
85
86/// An async wrapper with an optimized synchronous execution path.
87///
88/// It is used in combination with `Future` trait objects, such as
89/// `DynObject<dyn Future<Output=T>>`.
90///
91/// # Examples
92///
93/// ```rust
94/// # use dyn_utils::{DynObject, TrySync};
95///
96/// trait Callback {
97/// fn call(&self, arg: &str) -> TrySync<DynObject<dyn Future<Output = ()>>>;
98/// }
99///
100/// struct Print;
101/// impl Callback for Print {
102/// fn call(&self, arg: &str) -> TrySync<DynObject<dyn Future<Output = ()>>> {
103/// println!("{arg}");
104/// TrySync::Sync(())
105/// }
106/// }
107/// ```
108pub enum TrySync<F: Future> {
109 /// Optimized synchronous execution path.
110 Sync(F::Output),
111 /// Asynchronous wrapper
112 Async(F),
113 /// Synchronous execution path already polled
114 SyncPolled,
115}
116
117impl<F: Future> TrySync<F> {
118 /// # Safety
119 ///
120 /// `self` must be `Self::Sync` variant.
121 #[cfg_attr(coverage_nightly, coverage(off))] // Because of `unreachable_unchecked` branch
122 #[inline(always)]
123 unsafe fn take_sync(&mut self) -> F::Output {
124 match mem::replace(self, Self::SyncPolled) {
125 Self::Sync(res) => res,
126 // SAFETY: as per function contract
127 _ => unsafe { hint::unreachable_unchecked() },
128 }
129 }
130}
131
132impl<F: Future> Future for TrySync<F> {
133 type Output = F::Output;
134 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
135 // SAFETY: pinned data is not moved
136 match unsafe { self.get_unchecked_mut() } {
137 // SAFETY: res is `Self::Sync`
138 res @ TrySync::Sync(_) => Poll::Ready(unsafe { res.take_sync() }),
139 // SAFETY: `fut` is pinned as `self` is
140 TrySync::Async(fut) => unsafe { Pin::new_unchecked(fut) }.poll(cx),
141 _ => panic!("future polled after completion"),
142 }
143 }
144}
145
146#[cfg_attr(coverage_nightly, coverage(off))] // Because of `unreachable_unchecked` branch
147#[cfg(test)]
148mod tests {
149 use core::{
150 future::{Ready, ready},
151 pin::pin,
152 };
153
154 use futures::FutureExt;
155
156 use crate::TrySync;
157
158 #[test]
159 fn try_sync() {
160 for try_sync in [TrySync::Sync(42), TrySync::Async(ready(42))] {
161 assert_eq!(try_sync.now_or_never(), Some(42));
162 }
163 }
164
165 #[test]
166 #[should_panic(expected = "future polled after completion")]
167 fn try_sync_polled_after_completion() {
168 let mut try_sync = pin!(TrySync::<Ready<i32>>::Sync(42));
169 assert_eq!(try_sync.as_mut().now_or_never(), Some(42));
170 try_sync.now_or_never();
171 }
172}