cb_fut 0.1.0

Call function that return value via callback without a callback
Documentation

cb_fut

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.

Limitation

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.

How to use

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.
  2. 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.

Examples

Turn a function with callback on last parameter into a future.

// 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))));

assert_eq!(5, a);
assert_eq!(10, b);

The callback can be in anywhere in function signature. The callback placeholder just need to reflect that too.

// 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::from_secs(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::block_on(once!(func(1, ->(a, b), 2 + 3)));
assert_eq!(1, a);
assert_eq!(5, b);

In case the callback take no argument, we need to put -> ()

// a function that take no arguments callback
fn func(_v: i32, cb: impl FnOnce()) {
    std::thread::sleep(std::time::Duration::from_secs(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!

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::block_on(stream!(func(->(a, b), 2 + 3)).enumerate().for_each(|(i, fut)| {
    counter += 1;
    async move {
        let (a, b) = fut;
        assert_eq!(5, a);
        assert_eq!(5 * i as i32, b);
    }
}));

assert_eq!(5, counter);