【问题标题】:Early exit when matching result type匹配结果类型时提前退出
【发布时间】:2020-04-20 05:07:08
【问题描述】:

我有一个带有Result 类型参数的方法。我非常喜欢链接,所以我在参数上使用and_then。在某些时候,我想有条件地从and_then 内部返回整个方法(因此有机会可以调用另一个and_then 方法):

enum Good {
    Ok,
    Good,
    VeryGood,
}
enum Pizza {
    Tomato,
    Pineapple,
}
enum Burger {
    Cow,
}

enum Food {
    Pizza(Pizza),
    Burger(Burger),
}

fn main() {}

fn s(r: Result<Good, ()>) -> Result<Food, ()> {
    r.and_then(|o| {
        match o {
            // Should be called in the next and_then block
            Good::Ok => Ok(Pizza::Tomato),
            // Should be called in the next and_then block
            Good::Good => Ok(Pizza::Pineapple),
            Good::VeryGood => {
                // I am done. Don't call the next and_then block, but rather return the whole value to the caller.
                return Ok(Food::Burger(Burger::Cow));
            }
        }
    })
    .and_then(|p: Pizza| {
        // At this point, the closure input value should be pizza, because that's the only returned value
        Ok(Food::Pizza(p))
    })
}

Playground

我得到各种编译器错误:

error[E0308]: mismatched types
  --> src/main.rs:30:27
   |
30 |                 return Ok(Food::Burger(Burger::Cow));
   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Pizza`, found enum `Food`
   |
   = note: expected type `Pizza`
              found type `Food`

我希望有办法让它编译。我可以分解该方法并摆脱and_then,但也许有and_then 的方法。

在我的真实代码中,我有更多 and_then 将类型映射到其他类型、错误映射等,所以这是我面临的问题的简化再现路径。

这是我的代码库中复制粘贴的代码,显示了多个 and_then。我将来自外部库的错误映射到我自己的错误类型中,因此如果有错误可以自动返回。我想继续更改从and_then 获得的类型,这样我最终可以获得User 类型(尽管它目前不起作用)。一种选择是不链接块并创建单独的值,但我希望我可以直接将值返回给闭包内的调用者。

db_session
    .query_with_values(query, values)
    .map_err(|e| {
        error!("{:?}", e);
        TechnicalServerError::SERVER_RETRY
    })
    .and_then(|f| {
        f.get_body().map_err(|e| {
            error!("{:?}", e);
            TechnicalServerError::SERVER_RETRY
        })
    })
    .and_then(|b| b.into_rows().ok_or(TechnicalServerError::SERVER_RETRY))
    .and_then(|mut c| {
        if let Some(row) = c.pop() {
            User::try_from_row(row).map_err(|e| {
                error!("{:?}", e);
                TechnicalServerError::SERVER_INVALID
            })
        } else {
            return Ok(Login::Other(LoginResult::UNKNOWN_USER));
        }
    })
    .and_then(|u| {
        // 'u' should be of type 'user' at this point
        // some user code here...
    })

【问题讨论】:

  • 您发布的代码未编译。您能否对其进行编辑以更好地向我们展示工作流程?还有,你怎么这么早回来?有具体原因吗?对我来说,使用带有单个返回点的 map 会更好地服务此代码。
  • @DanielFath 查看我的编辑
  • 看来Is there any way to return from a function from inside a closure? 的答案可能会回答您的问题。如果没有,请edit您的问题来解释差异。否则,我们可以将此问题标记为已回答。
  • 您还应该阅读How do you define custom Error types in Rust?,其中讨论了如何使? 自动包装底层错误。

标签: rust


【解决方案1】:

查看您编辑的示例,我创建了一个approximate example

// ...
    .and_then(|b| b.into_rows().ok_or(TechnicalServerError::SERVER_RETRY))
    .and_then(|mut c| {
        if let Some(row) = c.pop() {
            User::try_from_row(row).map_err(|e| {
                error!("{:?}", e);
                TechnicalServerError::SERVER_INVALID
            })
        } else {
            return Ok(Login::Other(LoginResult::UNKNOWN_USER))
        }
    }).and_then(|u| {
        // 'u' should be of type 'user' at this point
        // some user code here...

我认为对于and_then 的作用存在基本的误解。

  • and_then 采用Result&lt;T, E&gt;,以及将T 转换为Result&lt;U, E&gt; 的函数(即FnOnce(T) -&gt; Result&lt;U, E&gt;。当您想同时操作Ok(val)Error 时使用此函数,例如你想改变重新映射错误并做一些值处理。

  • map 采用Result&lt;T, E&gt; 和一个将T 转换为另一个值U(即FnOnce(T) -&gt; U)的函数。当您想更改 Ok(val) 而不影响 Error 部分时,请使用此选项。

你说你想改变用户,这可能是这样的:

db_session
    .query_with_values(true)
    .map_err(|e| {
        println!("{:?}", e);
        MyError::ServerRetry
    })
    .and_then(|f| f.get_body(true).map_err(|e2|{
        println!("{:?}", e2);
        MyError::ServerRetry
    }))
    .and_then(|b| b.into_rows(true).ok_or(MyError::ServerRetry))
    .and_then(|mut c|{
        if let Some(row) = c.pop() {
            User::from_row(row).map_err(|e3| {
                println!("{:?}", e3);
                MyError::ServerRetry
            })
        } else {
            return Ok(User{ name: "X".to_string()})
        }
    })
    .map(|mut user| {
        user.name = "CHANGED".to_string();
        user
    });

Rust playground.

但是,正如您所见,查询的值将始终为Result&lt;User, Error&gt;,直接对该值进行操作的唯一方法是unwrap,但如果遇到错误则会出现恐慌。或者,您可以使用if let 来获取值而不会惊慌。

【讨论】:

    【解决方案2】:

    不是一个干净的。换成? 怎么样?

    fn s(r: Result<Good, ()>) -> Result<Food, ()> {
        let p = match r? {
            // Should be called in the next and_then block
            Good::Ok => Pizza::Tomato,
            // Should be called in the next and_then block
            Good::Good => Pizza::Pineapple,
            Good::VeryGood => {
                return Ok(Food::Burger(Burger::Cow));
            },
        };
    
        Ok(Food::Pizza(p))
    }
    

    【讨论】:

    • 这在我提供的代码中确实可以工作:) 但是,在我的“真实”代码中,我有更多的 and_then,它们是映射类型和错误,遗憾的是,这种方式还不够:(无论如何感谢您的回答,我会更新我的问题以使其更清楚
    • @J.Doe:那么你能创建一个更真实的例子/展示真实的代码吗?你不能像那样使用return,所以任何替代方案都可能需要更多的上下文。 (另外,这是针对稳定的 Rust 还是 #![feature]s 好吗?)
    • 为了简单起见,我链接了多个 and_then,有时,我只想说(在 and_then 块中):好的,如果某些事情是真的,只需将此值返回给调用者,否则只是调用下一个 and_then。这是稳定的锈蚀。总结一下:and_then 块中的 return 语句实际上不会返回函数,而是将给定类型映射到返回值,使其在下一个 and_then 块中可用?
    • @J.Doe: return 从作为参数传递给and_then 的闭包返回,是的。
    • @J.Doe 你能把例子改成有多个and_then吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-27
    • 1970-01-01
    • 2016-02-08
    • 1970-01-01
    相关资源
    最近更新 更多