This crate provide utility macros to convert a call to function that need a callback to handle return values into a function that either return a Future
that resolve to values or a Stream
that yield values. It introduce extra syntax -> ()
into functional call signature.
Following are limitations of this crate.
- If the callback is not intended to return a value, don't use these two macros. It'll break the function.
- If the function also return value, the returned value will be silently dropped.
- The macro support only single callback conversion. If the function take more than one callbacks to return value on different circumstances, it cannot be use.
- It will be slower compare to using original callback. This is because it require some kind of indirection to direct a callback through channel into a Future
.
This crate provide two macros.
1. once
- for convert a call to a function with a callback into a Future
that resolve to values which used to be return as callback parameters.
1. stream
- for convert a call to a function with a callback into a Stream
that yield values which usually pass to callback as parameters.
The main difference between the two is once
shall be used if a callback will be called exactly once while stream
shall be used if a callback will be called multiple times.
These two macro need a special syntax to identify number of expected return variables. The syntax is -> ()
which resemble the function signature for returning type, e.g. Fn(i32) -> ()
.
These conversion result in Future
that resolve to tuple of values. See examples below.
Turn a function with callback on last parameter into a future. ```rust // A function that need a callback fn func(v: i32, cb: impl FnOnce(i32, i32)) { std::thread::sleep(std::time::Duration::from_secs(2)); cb(v, v * 2) }
// Use once! to convert the call to func
to return a Future
instead.
// We use ->(a, b) to tell macro that Future
shall return two variables.
let (a, b) = futures::executor::block_on(once!(func(2 + 3, ->(a, b))));
asserteq!(5, a);
asserteq!(10, b);
The callback can be in anywhere in function signature. The callback placeholder just need to reflect that too.
rust
// A function that put callback in the middle between other two parameters
fn func(u: i32, cb: impl FnOnce(i32, i32), v: i32) {
std::thread::sleep(std::time::Duration::fromsecs(2));
cb(u, v)
}
// We use ->(a, b)
between 1, and, 2 + 3 to tell macro that this parameter is a callback and it take 2 parameters.
let (a, b) = futures::executor::blockon(once!(func(1, ->(a, b), 2 + 3)));
asserteq!(1, a);
asserteq!(5, b);
In case the callback take no argument, we need to put `-> ()`
rust
// a function that take no arguments callback
fn func(v: i32, cb: impl FnOnce()) {
std::thread::sleep(std::time::Duration::fromsecs(2));
cb()
}
// A callback placeholder with no argument
futures::executor::block_on(once!(func(2 + 3, -> ())));
If callback will be called multiple times, use `stream!`
rust
use futures::stream::StreamExt;
// A function that take callback as first argument.
// It'll call callback 5 times with two arguments, an original value and the original value times number of called.
fn func(mut cb: impl FnMut(i32, i32), v: i32) {
for i in 0..5 {
cb(v, v * i)
}
}
let mut counter = 0;
// stream!
will return CBStream
which implement Stream
trait. We use enumerate
and for_each
from StreamExt
trait to iterate over each values tuples that suppose to be passed to callback function.
// The for_each
method signature require a return value of type Future
for given callback. The final return value from for_each
is a single consolidated Future
which when resolve, all Future
s inside it are all resolved.
futures::executor::blockon(stream!(func(->(a, b), 2 + 3)).enumerate().foreach(|(i, fut)| {
counter += 1;
async move {
let (a, b) = fut;
asserteq!(5, a);
asserteq!(5 * i as i32, b);
}
}));
assert_eq!(5, counter); ```