0

I'm trying to create a generic function that has the impressive goal of wrapping an async closure. So for all intents and purposes, it would treat the closure as a black box and simply run some setup logic before and some logic after, depending on the return value of the closure that is passed in.

Here is an MRE example code I have put together so far. I was also testing it out in the Rust playground as well.

use std::future::Future;
use std::pin::Pin;

#[derive(Default)]
struct MyEvent {}

#[derive(Default)]
struct MyContext {
    pub fn_name: String,
}

pub struct HelpAnError {}

#[tokio::main]
async fn main() {
    let my_age: u8 = 29;

    let doing_work_hard = move |event: MyEvent, ctx: MyContext| async move {
        println!("I'm working hard, i promise!");
        println!("Ma i'm already {} yrs old!", my_age);
        Ok::<_, HelpAnError>(())
    };

    let doing_chores_hard = lets_wrap_da_closure(doing_work_hard);

    // does all the chores that Ma assigned to me
    let _ = doing_chores_hard(Default::default(), Default::default()).await;
}

fn lets_wrap_da_closure<
    C: Fn(MyEvent, MyContext) -> F,
    F: Future<Output = Result<(), HelpAnError>>,
>(
    do_work_hard: C,
) -> fn(MyEvent, MyContext) -> Pin<Box<dyn Future<Output = Result<(), HelpAnError>>>> {
    move |event: MyEvent, ctx: MyContext| {
        Box::pin(async move {
            println!("I'm doin my chores Ma, I promise!");
            // **help** - if I uncomment this it fails!
            // do_work_hard(event, ctx).await;
            println!("Yay! The chores are now complit.");
            Ok(())
        })
    }
}

I would appreciate any help or pointers on how to get this to work with the rust compiler, which doesn't seem too friendly to me here. I feel as I've struggled for hours but am not smart enough or skilled with rust enough to know how to satisfy the compiler rules.

I also checked out Return an async function from a function in Rust and have got the tip to use Box::pin accordingly. However I'm having trouble implementing it for my specific use case, i.e. where I want to create a generic function to wrap a callable that returns a Future in particular. Thanks and let me know if I need to clarify on anything.


Update: Thanks to help from @Jakub. Here is my revised (working) code below:

use std::future::Future;
use std::pin::Pin;

#[derive(Default)]
struct MyEvent {}

#[derive(Default)]
struct MyContext {
    pub fn_name: String,
}

#[derive(Debug)]
pub struct HelpAnError {}

#[tokio::main]
async fn main() {
    let my_age: u8 = 29;

    let doing_work_hard = move |event: MyEvent, ctx: MyContext| async move {
        println!("I'm working hard, i promise!");
        println!("Ma i'm already {} yrs old!", my_age);
        Ok::<_, HelpAnError>(())
    };

    let doing_chores_hard = lets_wrap_da_closure(doing_work_hard);

    // does all the chores that Ma assigned to me
    let _ = doing_chores_hard(Default::default(), Default::default()).await;
}

fn lets_wrap_da_closure<
    'a,
    C: Fn(A1, A2) -> F,
    A1,
    A2,
    E: std::fmt::Debug,
    F: Future<Output = Result<(), E>> + 'a,
>(
    do_work_hard: C,
) -> impl Fn(A1, A2) -> Pin<Box<dyn Future<Output = Result<(), E>> + 'a>> {
    move |event: A1, ctx: A2| {
        let fut = do_work_hard(event, ctx);
        Box::pin(async move {
            println!("I'm doin my chores Ma, I promise!");
            // it WORKS! wow, amazing ~
            match fut.await {
                Ok(_) => println!("All systems are a GO!"),
                Err(e) => println!("I ran into issue doing my chores: {:?}", e),
            };
            println!("Yay! The chores are now complit.");
            Ok(())
        })
    }
}

Side note: it would've been nice to wrap the dyn Future<Output = Result<(), HelpAnError>> into a generic or perhaps just reuse the F, but at this point I'm just glad that was able to get it working :-)

1 Answer 1

1
use std::future::Future;
use std::pin::Pin;

#[derive(Default)]
struct MyEvent {}

#[derive(Default)]
struct MyContext {
    pub fn_name: String,
}

pub struct HelpAnError {}

#[tokio::main]
async fn main() {
    let my_age: u8 = 29;

    let doing_work_hard = move |event: MyEvent, ctx: MyContext| async move {
        println!("I'm working hard, i promise!");
        println!("Ma i'm already {} yrs old!", my_age);
        Ok::<_, HelpAnError>(())
    };

    let doing_chores_hard = lets_wrap_da_closure(doing_work_hard);

    // does all the chores that Ma assigned to me
    let _ = doing_chores_hard(Default::default(), Default::default()).await;
}

fn lets_wrap_da_closure<
    C: Fn(MyEvent, MyContext) -> F + 'static,
    F: Future<Output = Result<(), HelpAnError>> + 'static,
>(
    do_work_hard: C,
) -> impl Fn(MyEvent, MyContext) -> Pin<Box<dyn Future<Output = Result<(), HelpAnError>>>> {
    move |event: MyEvent, ctx: MyContext| {
        // this is the only way i can satisfy the compiler, do not worry,
        // future will get executed only upon await and this call is just 
        // creating the future we are passing
        let fut = do_work_hard(event, ctx);
        Box::pin(async move {
            println!("I'm doin my chores Ma, I promise!");
            // **help** - if I uncomment this it fails!
            drop(fut.await);
            println!("Yay! The chores are now complit.");
            Ok(())
        })
    }
}

Sign up to request clarification or add additional context in comments.

3 Comments

Amazing! I'll have to read up more on this specific use case, assuming that it's mentioned in the Rust book or elsewhere, but at this point I'm just really glad that what I proposed was actually achievable in some manner. Props to you my friend!
Just a minor note, but what I found is that instead of the explicit drop syntax, you can also have it like let _ = fut.await and ignore the explicit Result. I think the only reason you can't use the ? shorthand here is because I haven't correctly implemented Error for my HelpAnError type. I'm not entirely sure about the difference between drop and this approach, but if I understand correctly it should have more or less the same result.
It's just my preference, it mainly proves useful when you want to make a shorthand branch of match statement but not return any value from the branch.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.