首页 > 解决方案 > 可以将此处的结果匹配替换为 map_err 和“?”

问题描述

我有一些看起来像这样的代码(非常简化的版本)。函数接受两个类型的函数参数,LoadClientCheckApproval返回错误或字符串。

pub struct Client {
    pub id: String,
}

pub enum MyErr {
    RequiresApproval(Client, String),
    LoadFailed,
}

pub fn authorize<LoadClient, CheckApproval>(load_client: LoadClient, check_approval: CheckApproval) -> Result<String, MyErr> 
where
   LoadClient: FnOnce(String) -> Result<Client, String>,
   CheckApproval: for<'a> FnOnce(&'a Client, &str) -> Result<&'a str, ()>,
{
    let client = load_client("hello".to_string()).map_err(|_| MyErr::LoadFailed)?;
    let permission = "something";

    // This doesn't compile
    // let authorized = check_approval(&client, permission).map_err(|_| MyErr::RequiresApproval(client, permission.to_string()))?;
    // Ok(authorized.to_string())

    // This version does
    match check_approval(&client, permission) {
        Err(_) => Err(MyErr::RequiresApproval(client, permission.to_string())),
        Ok(authorized) => Ok(authorized.to_string()),
    }
}

我想?check_approval调用一起使用(如注释掉的代码所示)以获得更简单的代码并避免额外的嵌套 -Ok最终匹配中的分支实际上是一个更长的块。

不幸的是,这不能编译:

error[E0505]: cannot move out of `client` because it is borrowed
  --> src/lib.rs:19:66
   |
19 |     let authorized = check_approval(&client, permission).map_err(|_| MyErr::RequiresApproval(client, permission.to_string()))?;
   |                                     -------              ------- ^^^                         ------ move occurs due to use in closure
   |                                     |                    |       |
   |                                     |                    |       move out of `client` occurs here
   |                                     |                    borrow later used by call
   |                                     borrow of `client` occurs here

这些看起来很相似(对于我未经训练的眼睛)。借来的引用不是被调用client的时候还回来了吗?map_err

我的主要问题:有没有办法绕过这个并在不使用的情况下编写代码match

锈游乐场链接

标签: rust

解决方案


虽然您的两个版本的代码在语义上是等效的,但它们对于编译器来说实际上是完全不同的。

失败的调用Result::map_err()一个闭包,该闭包捕获client. 也就是说,client被移到闭包中,但在调用时被借用check_approval()。这就是错误所在,不能移动借来的值。

你可能认为这个借用应该在函数返回时完成,但事实并非如此,因为它的返回类型Result<&'a str, ()>正是'a那个借用的生命周期。client只要它'a存在,借用就会延长。这就是为什么您的第二个版本有效的原因:当您匹配您Result的 时,'a不会扩展到Err(())分支,只会扩展Ok(&'a str)Err(())能够client自由移动。

有没有办法绕过这个并在不使用的情况下编写代码match

好吧,您正在调用authorized.to_string()返回的&'a str并将其转换为拥有的String. 因此,如果您可以将CheckApproval约束更改为:

CheckApproval: FnOnce(&Client, &str) -> Result<String, ()>,

问题就消失了。

如果你不能改变它,另一种选择是to_string()在将其client移入闭包之前执行,在它造成伤害之前完成借用:

let authorized = check_approval(&client, permission)
    .map(|a| a.to_string())
    .map_err(|_| MyErr::RequiresApproval(client, permission.to_string()))?;
Ok(authorized)

推荐阅读