owned-future 0.1.0

Turn borrowed futures into owned futures
Documentation

owned-future

github crates.io docs.rs build status

This tiny crate contains helpers to turn a borrowed future into an owned one.

Motivation

Take tokio::sync::Notify as an example. It's often useful to call Notify::notified from the main thread and then pass it to a spawned thread. Doing this guarantees the resulting Notified is watching for calls to Notify::notify_waiters prior to the thread being spawned. However this isn't possible with as notified borrows the Notify.

use std::sync::Arc;
use tokio::sync::Notify;

let notify = Arc::new(Notify::new());

// Spawn a thread that waits to be notified
{
    let notify = notify.clone();
    // Start listening before we spawn
    let notified = notify.notified();
    tokio::spawn(async move {
        // wait for our listen to complete
        notified.await; // <-- fails because we can't move `notified`
    });
}

// notify the waiting threads
notify.notify_waiters();

At present, there's no way to do this kind of borrow and then move, and while there are many crates available to help turn this problem into a self-borrowing one, those solutions require unsafe code with complicated covariance implications. This crate is instead able to solve this simple case with no unsafe, and only 1-2 lines of unsafe code for more complex cases with no covariance problems. Here is the solution to the above problem:

use std::sync::Arc;
use tokio::sync::Notify;
use owned_future::make;

let notify = Arc::new(Notify::new());

// Spawn a thread that waits to be notified
{
    // Start listening before we spawn
    let get_notified = owned_future::get!(fn(n: &mut Arc<Notify>) -> () {
        n.notified()
    });
    let notified = make(notify.clone(), get_notified);
    tokio::spawn(async move {
        // wait for our listen to complete
        notified.await;
    });
}

// notify the waiting threads
notify.notify_waiters();

Technical Details

So how does this work exactly? Well, while rust usually doesn't let you move a borrowed value, there's one exception. Pinned futures. Once async code has been transformed into a Pined future, it can invoke the borrow operation, but still be freely moved around. Essentially what this crate does is a prettied up version of this:

let mut wrapped_notified = Box::pin(async move {
    let notified = notify.notified();

    // This prevents us from driving the future to completion on the first poll
    force_pause().await;

    future.await
});

// Drive the future up to just past our `force_pause`
wrapped_notified.poll_once()

tokio::spawn(async move {
    // wait for our listen to complete
    wrapped_notified.await;
});

The more complex wrappers have a little bit more machinery to handle auxiliary values and errors, and the Async* helpers need a little bit of pin-projection and poll handling, but ultimately the core logic boils down to something like the above.

License

Licensed under MIT license (LICENSE or https://opensource.org/licenses/MIT)