desaturate/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(
4    not(all(
5        feature = "std",
6        feature = "macros",
7        feature = "async",
8        feature = "blocking"
9    )),
10    doc = r#"<div class="warning">This documentation will be incomplete, because of missing feature flags!</div>"#
11)]
12#![cfg_attr(
13    all(feature = "macros", feature = "async", feature = "blocking"),
14    doc = r#"
15This crate aims at reducing the coloring of rust functions, by simplifying the process of
16creating functions which functions both as async and as blocking functions. This is performed
17with the [`Desaturated`] trait, which implements [`IntoFuture`] and [`Blocking`], depending on
18which feature flags are set.
19
20The idea is that a desaturated function can be created by combining the `async` color with the
21blocking color, as such:
22```
23use desaturate::{Desaturated, IntoDesaturated};
24fn do_something() -> impl Desaturated<()> {
25    async {
26        // Do something here
27    }.desaturate(|| {
28        // Do the same thing here
29    })
30}
31```
32
33Now, this doesn't reduce code duplication, per se, but it can enable it, especially when used
34with the [`desaturate`] macro. This allows us to create functions like this:
35```
36use desaturate::{desaturate, Blocking};
37
38#[desaturate]
39async fn do_the_thing(arg: i32) -> i32 {
40    arg * 2
41}
42
43#[desaturate]
44async fn do_something(arg1: i32, arg2: i32) -> i32 {
45    do_the_thing(arg1).await + do_the_thing(arg2).await
46}
47
48fn main() {
49    let result = do_something(5, 10).call();
50    println!("I got to play with the async functions, and got {result}");
51}
52```
53
54This also takes care of lifetimes, so you can make functions which track (or ignore) lifetimes.
55```
56use desaturate::{desaturate, Blocking};
57
58#[desaturate]
59async fn add_1(i: &mut i32) -> i32 {
60    let old = *i;
61    *i += 1;
62    old
63}
64
65fn main() {
66    let mut value = 5;
67    println!("Counting: {}", add_1(&mut value).call());
68    println!("Counting: {}", add_1(&mut value).call());
69    assert_eq!(value, 7);
70}
71```
72
73It can also be used on member functions, as such:
74```
75use desaturate::{desaturate, Blocking};
76
77struct MyType<'a> {
78    inner: i32,
79    pointer: &'a i32,
80}
81
82impl<'a> MyType<'a> {
83    #[desaturate(lifetime = "'a")]
84    async fn get_inner_mut(&mut self) -> &mut i32 {
85        &mut self.inner
86    }
87    #[desaturate]
88    async fn get_pointer(&self) -> &'a i32 {
89        self.pointer
90    }
91}
92"#
93)]
94
95use core::future::{Future, IntoFuture};
96
97#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
98#[cfg(feature = "std")]
99pub mod boxed;
100
101#[macro_use]
102mod macros;
103use macros::*;
104
105#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
106#[cfg(feature = "macros")]
107/// This macro will try to automatic add the trait [`Desaturated`] to a function signature.
108#[cfg_attr(
109    all(
110        feature = "macros",
111        feature = "async",
112        feature = "blocking",
113        feature = "std"
114    ),
115    doc = r#"
116```
117use desaturate::{Blocking, desaturate, IntoDesaturated, IntoDesaturatedWith, Desaturated, boxed::BoxedDesaturated};
118#[desaturate]
119async fn do_something(arg: i32) -> i32 {
120    other_function(arg).await
121}
122
123#[desaturate]
124async fn other_function(arg: i32) -> i32 {
125    arg * 2
126}
127
128# #[tokio::main]
129async fn main() {
130    assert_eq!(do_something(5).await, do_something(5).call())
131}
132```"#
133)]
134///
135/// Available attributes:
136/// ```
137/// # use desaturate::{desaturate, Blocking};
138/// // Dump the result generated code into stderr:
139/// #[desaturate(debug_dump)]
140/// # async fn do_nothing() {}
141/// # struct Test<'a>(&'a i32);
142/// # impl<'a> Test<'a> {
143///
144/// // Use the lifetime 'a instead of a generated one for the lifetime of impl Desaturated<_>.
145/// #[desaturate(lifetime = "'a")]
146/// # async fn inner(&self) -> &i32 { self.0 }
147/// # }
148///
149/// // Have conditional compilation based on current function color
150/// #[desaturate(only_blocking_attr = "for_blocking", only_async_attr = "for_async")]
151/// async fn target_specific(arg: i32) -> i32 {
152///     let something;
153///     #[for_blocking] { something = arg + 1 }
154///     #[for_async] { something = arg - 1 }
155///     something
156/// }
157/// #
158/// # #[tokio::main]
159/// # async fn main() {
160/// # assert_eq!(6, target_specific(5).call());
161/// # assert_eq!(4, target_specific(5).await);
162/// # }
163/// ```
164/// Multiple attributes can be added with comma separation.
165pub use desaturate_macros::*;
166
167#[must_use]
168pub trait Blocking<Output: Sized> {
169    fn call(self) -> Output;
170}
171impl<Output, T: FnOnce() -> Output> Blocking<Output> for T {
172    fn call(self) -> Output {
173        (self)()
174    }
175}
176
177mod private {
178    pub trait Sealed<Output> {}
179}
180
181features! {async fn: create_asyncable!{ T => Blocking<T> + IntoFuture<Output = T> }}
182
183features! {!async fn: create_asyncable!{ T => Blocking<T> }}
184
185features! {async !fn: create_asyncable!{ T => IntoFuture<Output = T> }}
186
187features! {!async !fn: create_asyncable!{ T => }}
188
189features! {!async !fn:
190    impl<O> Desaturated<O> for () {}
191    impl<O> private::Sealed<O> for () {}
192}
193
194#[doc(hidden)]
195pub trait AsyncFnOnce<'a, Args: 'a, Out> {
196    type Output: Future<Output = Out> + 'a;
197    fn call(self, args: Args) -> Self::Output;
198}
199
200impl<'a, Args: 'a, Out, Fun, Fut> AsyncFnOnce<'a, Args, Out> for Fun
201where
202    Fun: FnOnce(Args) -> Fut,
203    Fut: Future<Output = Out> + 'a,
204{
205    type Output = Fut;
206    fn call(self, args: Args) -> Self::Output {
207        (self)(args)
208    }
209}
210
211pub trait IntoDesaturatedWith<'a, Args: 'a, Output>: AsyncFnOnce<'a, Args, Output> + 'a {
212    fn desaturate_with(
213        self,
214        args: Args,
215        fun: impl 'a + FnOnce(Args) -> Output,
216    ) -> impl Desaturated<Output> + 'a;
217}
218
219impl<'a, O: 'a, A: 'a, F: 'a + AsyncFnOnce<'a, A, O>> IntoDesaturatedWith<'a, A, O> for F {
220    features! {async fn:
221        #[inline(always)]
222        fn desaturate_with(self, args: A, fun: impl FnOnce(A) -> O + 'a) -> impl Desaturated<O> + 'a {
223            struct Holder<'a, Output, Args: 'a, NormalFunc: FnOnce(Args) -> Output, AsyncFunc: AsyncFnOnce<'a, Args, Output>> {
224                args: Args,
225                fun: NormalFunc,
226                fut: AsyncFunc,
227                phantom: core::marker::PhantomData<&'a ()>,
228            }
229            impl<'a, Output, Args: 'a, NormalFunc: FnOnce(Args) -> Output, AsyncFunc: AsyncFnOnce<'a, Args, Output>> IntoFuture for Holder<'a, Output, Args, NormalFunc, AsyncFunc> {
230                type Output = Output;
231
232                type IntoFuture = AsyncFunc::Output;
233
234                #[inline(always)]
235                fn into_future(self) -> Self::IntoFuture {
236                    self.fut.call(self.args)
237                }
238            }
239            impl<'a, Output, Args: 'a, NormalFunc: FnOnce(Args) -> Output, AsyncFunc: AsyncFnOnce<'a, Args, Output>> Blocking<Output> for Holder<'a, Output, Args, NormalFunc, AsyncFunc> {
240                #[inline(always)]
241                fn call(self) -> Output {
242                    (self.fun)(self.args)
243                }
244            }
245            impl<'a, Output, Args: 'a, NormalFunc: FnOnce(Args) -> Output, AsyncFunc: AsyncFnOnce<'a, Args, Output>> private::Sealed<Output> for Holder<'a, Output, Args, NormalFunc, AsyncFunc> {}
246            Holder {
247                args,
248                fun,
249                fut: self,
250                phantom: Default::default(),
251            }
252        }
253    }
254    features! {async !fn:
255        #[inline(always)]
256        fn desaturate_with(self, args: A, _: impl FnOnce(A) -> O) -> impl Desaturated<O> + 'a {
257            struct Holder<Out, T: Future<Output = Out>>(T);
258            impl<Out, T: Future<Output = Out>> private::Sealed<Out> for Holder<Out, T> {}
259            impl<Out, T: Future<Output = Out>> IntoFuture for Holder<Out, T> {
260                type Output = Out;
261
262                type IntoFuture = T;
263
264                #[inline(always)]
265                fn into_future(self) -> T {
266                    self.0
267                }
268            }
269            Holder(self.call(args))
270        }
271    }
272    features! {!async fn:
273        #[inline(always)]
274        fn desaturate_with(self, args: A, fun: impl FnOnce(A) -> O + 'a) -> impl Desaturated<O> + 'a {
275            struct Holder<'a, Output, Args: 'a, Function: FnOnce(Args) -> Output> {
276                args: Args,
277                fun: Function,
278                phantom: core::marker::PhantomData<&'a ()>,
279            }
280            impl<'a, Output, Args: 'a, Function: FnOnce(Args) -> Output> private::Sealed<Output> for Holder<'a, Output, Args, Function> {}
281            impl<'a, Output, Args: 'a, Function: FnOnce(Args) -> Output> Blocking<Output> for Holder<'a, Output, Args, Function> {
282                #[inline(always)]
283                fn call(self) -> Output {
284                    (self.fun)(self.args)
285                }
286            }
287            Holder {
288                args,
289                fun,
290                phantom: Default::default(),
291            }
292        }
293    }
294    features! {!async !fn:
295        #[inline(always)]
296        fn desaturate_with(self, _: A, _: impl FnOnce(A) -> O) -> impl Desaturated<O> +'a {
297            ()
298        }
299    }
300}
301
302pub trait IntoDesaturated<'a, Output>: IntoFuture<Output = Output> + 'a {
303    fn desaturate(self, fun: impl FnOnce() -> Output + 'a) -> impl Desaturated<Output> + 'a;
304}
305
306impl<'a, Output: 'a, F: Future<Output = Output> + 'a> IntoDesaturated<'a, Output> for F {
307    #[inline(always)]
308    fn desaturate(self, fun: impl FnOnce() -> Output + 'a) -> impl Desaturated<Output> + 'a {
309        (|()| self).desaturate_with((), |()| fun())
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    #[allow(unused_imports)]
316    use super::{Blocking, Desaturated, IntoDesaturated, IntoDesaturatedWith};
317    #[allow(unused_imports)]
318    use core::sync::atomic::{AtomicBool, Ordering};
319    #[test]
320    #[cfg_attr(not(feature = "blocking"), ignore)]
321    fn normal_returns_right() {
322        #[cfg(feature = "blocking")]
323        {
324            let async_executed = AtomicBool::new(false);
325            let normal_executed = AtomicBool::new(false);
326            let async_stuff = async {
327                async_executed.store(true, Ordering::Relaxed);
328                5
329            };
330            let sync_stuff = || {
331                normal_executed.store(true, Ordering::Relaxed);
332                5
333            };
334            let desaturated = async_stuff.desaturate(sync_stuff);
335            assert_eq!(5, desaturated.call());
336            assert_eq!(false, async_executed.load(Ordering::Relaxed));
337            assert_eq!(true, normal_executed.load(Ordering::Relaxed));
338        }
339    }
340    #[tokio::test]
341    #[cfg_attr(not(feature = "async"), ignore)]
342    async fn async_returns_right() {
343        #[cfg(feature = "async")]
344        {
345            let async_executed = AtomicBool::new(false);
346            let normal_executed = AtomicBool::new(false);
347            let async_stuff = async {
348                async_executed.store(true, Ordering::Relaxed);
349                5
350            };
351            let sync_stuff = || {
352                normal_executed.store(true, Ordering::Relaxed);
353                5
354            };
355            let desaturated = async_stuff.desaturate(sync_stuff);
356            assert_eq!(5, desaturated.await);
357            assert_eq!(true, async_executed.load(Ordering::Relaxed));
358            assert_eq!(false, normal_executed.load(Ordering::Relaxed));
359        }
360    }
361    fn do_stuff(with: &i32) -> impl Desaturated<i32> + '_ {
362        let async_stuff = async { *with * 2 };
363        let sync_stuff = || *with * 2;
364        async_stuff.desaturate(sync_stuff)
365    }
366    fn do_stuff_with_pointer<'a>(with: &'a i32) -> impl Desaturated<i32> + '_ {
367        let async_stuff = |var: &'a i32| async move { *var * 2 };
368        let sync_stuff = |var: &'a i32| *var * 2;
369        async_stuff.desaturate_with(with, sync_stuff)
370    }
371    fn do_stuff_with_pointer_in_inner_function<'a>(with: &'a i32) -> impl Desaturated<i32> + '_ {
372        async fn async_stuff(var: &i32) -> i32 {
373            *var * 2
374        }
375        fn sync_stuff(var: &i32) -> i32 {
376            *var * 2
377        }
378        async_stuff.desaturate_with(with, sync_stuff)
379    }
380    #[test]
381    #[cfg_attr(not(feature = "blocking"), ignore)]
382    fn can_take_pointer() {
383        #[cfg(feature = "blocking")]
384        {
385            assert_eq!(20, do_stuff(&10).call());
386            let arg = 30;
387            assert_eq!(60, do_stuff_with_pointer(&arg).call());
388            assert_eq!(60, do_stuff_with_pointer_in_inner_function(&arg).call());
389        }
390    }
391    #[tokio::test]
392    #[cfg_attr(not(feature = "async"), ignore)]
393    async fn async_can_take_pointer() {
394        #[cfg(feature = "async")]
395        {
396            assert_eq!(20, do_stuff(&10).await);
397            let arg = 30;
398            assert_eq!(60, do_stuff_with_pointer(&arg).await);
399            assert_eq!(60, do_stuff_with_pointer_in_inner_function(&arg).await);
400        }
401    }
402    #[test]
403    #[cfg_attr(any(feature = "async", feature = "blocking"), ignore)]
404    fn it_always_builds() {
405        _ = do_stuff(&10)
406    }
407}