简体   繁体   中英

How can I alter the behavior of a function based on the return type of a closure passed as an argument?

I have a function that returns the result of a curried function:

async fn runit<F, Fut, Ret>(cb: F) -> Result<Ret, MyError>
where
    F: FnOnce() -> Fut,
    Fut: Future<Output = Ret>,
{
    Ok(cb().await)
}

Sometimes F may return a Result<Ret, MyError> , requiring the caller to unwrap the returned value twice. Is there a way I can write this such that the function automatically detects whether F already returns the right type and avoids the call to Ok ?

You might be able to use a trait with separate blanket implementations for Result and other types. Something like:

pub trait IntoResult<T, E> {
    fn into_result(self) -> Result<T, E>;
}

impl<T, E> IntoResult<T, E> for Result<T, E> {
    fn into_result(self) -> Result<T, E> {
        self
    }
}

impl<T, E> IntoResult<T, E> for T {
    fn into_result(self) -> Result<T, E> {
        Ok(self)
    }
}

Then you can implement runit to call into_result() :

async fn runit<F, Fut, Ret, RawRet>(cb: F) -> Result<Ret, MyError>
where
    F: FnOnce() -> Fut,
    Fut: Future<Output = RawRet>,
    RawRet: IntoResult<Ret, MyError>,
{
    cb().await.into_result()
}

Now Rust will be able to infer Ret that satisfies the IntoResult trait, effectively eliminating the inner Result :

// one unwrap needed for Result<usize, MyError>
let _x: usize = runit(|| async { 1 }).await.unwrap();
// two unwraps needed because closure returns Result whose error is not MyError
let _y: usize = runit(|| async { Ok::<_, std::io::Error>(1usize) })
    .await
    .unwrap()
    .unwrap();
// one unwrap enough because closure returns Result<usize, MyError>
let _z: usize = runit(|| async { Ok::<_, MyError>(1usize) }).await.unwrap();

Playground

Before using this in production, I suggest being very careful with this kind of "cleverness". While it looks really nice when it works, it can complicate the signatures of functions and sometimes it can require type hints that would otherwise not be necessary. It is often simpler to use something like anyhow::Error to coalesce results of incompatible errors into one result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM