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#[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)]
134pub 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}