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 -> ()
and -> () ->
into macro call signature.
Following are limitations of this crate.
- If the callback is not intended for 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 four macros in two categories.
It use futures::channel::unbound
to communicate between function callback and Future
. There is two macros in this category.
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.
It use two channels to keep sync between each CBBlockResult
and callback. It spawn a new thread to execute the function and block the function waiting for caller to execute method return_value
on CBBlockResult
or until the CBBlockResult
is dropped. There's two macros in this category.
1. once_blocked
- to get a Future
of result from a call to a function with a callback that return something back to function. It is different from once
macro on that it block the function until the result is drop or method return_value
is explicitly called. This macro will join the thread executing function when the CBBlockResult
is drop.
1. stream_blocked
- to get a CBStreamBlocked
that yield a result, CBBlockResult
, contains values similar to what was send to a callback. The result is different from regular stream
macro on that it block a function until the result is drop or explicitly call return_value
method.
The main difference between the two is once
and once_blocked
shall be used if a callback will be called exactly once while stream
and stream_blocked
shall be used if a callback will be called multiple times.
These two category need a special syntax to identify number of expected return variables. The syntax is -> ()
and ->()->v
. The prior resemble the function signature for returning type, e.g. Fn(i32) -> ()
. The later is extended from prior on that the callback itself return something back to the function which might affect execution of that function. The ->v
part will be used as default value when caller doesn't call return_value
method. This is to guarantee that the function will receive value back to resume the execution.
These conversion result in Future
or Stream
that will 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);
}
}));
asserteq!(5, counter);
The callback which has side effect on the function after callback is executed.
rust
fn func(v: i32, cb: impl FnOnce(i32, i32) -> i32) {
if cb(v, v * 2) == 0i32 {
dbg!("Ok !");
} else {
panic!("Something wrong")
}
}
let mut ret = futures::executor::blockon(onceblocked!(func(2 + 3, ->(a, b) -> 1i32)));
let (a, b) = *ret;
asserteq!(5, a);
asserteq!(10, b);
if a + b == 15 && a * b == 50 {
ret.returnvalue(0).unwrap();
}
asserteq!(ret.returnvalue(0).unwrap_err(), super::AlreadyReturnError);
The callback which control Stream control flow
rust
use futures::stream::StreamExt;
fn func(u: i32, mut cb: impl FnMut(i32, i32)->i32, v: i32) {
let mut j = 0;
while j < 5 {
j = cb(u + j, v * j)
}
}
let mut counter = 0;
futures::executor::blockon(cbfut::streamblocked!(func(2 * 3, ->(a, b)->0i32, 2 + 3)).enumerate().foreach(|(i, mut fut)| { counter += 1; async move { let (a, b) = *fut; asserteq!(2 * 3 + i as i32, a); asserteq!((2 + 3) * i as i32, b); fut.return_value(i as i32 + 1); } })); ```