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}