首页 > 解决方案 > 选项,and_then() 和元组

问题描述

我相信有一种方法可以“干净地”处理这个问题,我只是不太明白。

use git2::Repository;

// Prints out the current branch and sha if it exists.
fn report_repo() -> () {
    Repository::open(".")
        .ok()
        .and_then(branch_and_sha)
        .and_then(|branch_sha| => {  // Fails here with E0061
            let (branch, sha) = branch_sha;
            println!("Branch={} sha={}", branch, sha);
            None
        });
}

fn branch_and_sha(repo: Repository) -> Option<(String, String)> {
    match repo.head().ok() {
        Some(reference) => {
            match (reference.name(),  reference.target()){
                (Some(branch), Some(sha)) => Some((branch.to_string(), sha.to_string())),
                _ => None
            }
        },
        None => None
    }
} 

出现的错误是E0061,我认为这是因为 Option 返回的“值”branch_and_sha()是一个元组。branch_and_sha()有效地说,“如果有一个存储库,获取它的引用,如果存在,如果它同时具有名称(分支)和目标(sha),则返回Option<(String, String)>带有该信息的 an - 否则返回None。并且报告功能想要做如果可以找到所有Repository, 分支和 sha 的东西 - 否则什么都没有。(它不应该出错或恐慌。)

在某种程度上,这是人为的——它是一个乐观报告功能的例子,类似于我想写的几个。我正在寻找一种干净,惯用的方式来做到这一点。关键点是“几个深度和几个分支可能会返回None,这应该会导致无操作,否则会提供特定的(叶)信息。” 具体错误是我应该如何处理该and_then功能,很难找到类似的问题。

标签: rust

解决方案


首先,你有一个小错字。Rust 中的闭包不使用=>. 所以你的闭包应该看起来更像

.and_then(|branch_sha|  {  // Note: No => here
  let (branch, sha) = branch_sha;
  println!("Branch={} sha={}", branch, sha);
  None
});

那么我们得到的错误是

  --> so_cleanly.rs:15:10
   |
15 |         .and_then(|branch_sha| {
   |          ^^^^^^^^ cannot infer type for type parameter `U` declared on the associated function `and_then`
   |

and_then用两个通用参数声明:Uand F(从技术上讲,还有T,但这是由接收器的类型决定的self,所以我们不用担心)。现在,F是闭包的类型,并且始终由参数确定。另一方面,U是闭包的返回类型。

闭包必须返回一个Option<U>. Rust 需要查看闭包并确定它的返回类型是什么。关闭返回什么?它返回None,并且None可以是Option<U>任何U存在的。Rust 不知道该使用哪一个。我们需要告诉它。我们可以在我们返回的线路上做到这None一点

None as Option<()>

或在and_then通话本身中。

.and_then::<(), _>(|branch_sha| { ... })

但是,编译器提出了一个非常有效的观点。and_then和 company 产生 type 的结果Option,你忽略它。您正在编写一段具有副作用且不产生值的代码,这是明智的,但您正在使用旨在返回值的功能接口。它可以做到,但它可能不是惯用的。()在意识到返回值不是错字之前,我不得不查看您的代码几次。

一种选择是Option<()>从您的report_repo. 内部()表示我们不关心除了副作用之外的任何事情,并且Option让调用者report_repo处理(或忽略)过程中发生的任何错误,而您当前的函数只是无条件地抑制所有错误。

fn report_repo() -> Option<()> {
  Repository::open(".")
    .ok()
    .and_then(branch_and_sha)
    .map(|branch_sha| {
      let (branch, sha) = branch_sha;
      println!("Branch={} sha={}", branch, sha);
      // Implicit return of () here, which we could do explicitly if we wanted
    })
}

我在这里做了一些微妙的改变。返回类型是Option<()>现在。根据这一点,函数内的行尾没有分号(我们正在返回该值)。最后,最后一个and_thenmap,因为最后一步不能失败,只是在 上做一些工作Some

这是一个改进,但它可能仍然不是我编写这个函数的方式。

相反,如果您正在执行副作用代码,请考虑使用?operator,它确实and_thenmap恶作剧但保持控制流相对线性。and_then它的朋友非常适合构造值,但你的函数的重点是它应该读起来像一个指令序列,而不是一个值的构造函数。这就是我编写该函数的方式。

fn report_repo() -> Option<()> {
  let repo = Repository::open(".").ok()?;
  let (branch, sha) = branch_and_sha(repo)?;
  println!("Branch={} sha={}", branch, sha);
  Some(())
}

以 a 结尾的每一行都?有效地表示“如果这件事是None,请立即返回None。否则,请继续。” 但是粗略的看一下代码是“打开 repo、branch 和 sha,然后打印”,这正是你希望人们一眼就能看到的。

如果我们想真正做到这一点,我们可能应该 return Result<(), Error>,这Error是一些更详细的错误类型,但这对于这个简单的示例代码片段来说太过分了。


推荐阅读