如何用嵌套的“match”和返回“Result”的守卫来简化这个 Rust 代码?

How to simplify this Rust code with nested `match` with guards returning `Result`s?

提问人:Sridhar Ratnakumar 提问时间:7/12/2023 最后编辑:Sridhar Ratnakumar 更新时间:7/13/2023 访问量:93

问:

有没有更简单的方法来编写这个 Rust 代码?有关完整上下文,请参阅此 PR。

impl FromArgValue for FlakeRef {
    fn from_arg_value(s: &str) -> std::result::Result<FlakeRef, String> {
        match Url::parse(s) {
            Ok(url)
                if url.scheme() == "https" && url.host() == Some(Host::Domain("github.com")) =>
            {
                match url.path_segments().map(|c| c.collect::<Vec<_>>()) {
                    None => Ok(FlakeRef::Flake(s.to_string())),
                    Some(paths) => match paths[..] {
                        [user, repo, "pull", pr_] => match pr_.parse() {
                            Ok(pr) => Ok(FlakeRef::GithubPR {
                                owner: user.to_string(),
                                repo: repo.to_string(),
                                pr: pr,
                            }),
                            Err(_) => Ok(FlakeRef::Flake(s.to_string())),
                        },
                        _ => Ok(FlakeRef::Flake(s.to_string())),
                    },
                }
            }
            _ => Ok(FlakeRef::Flake(s.to_string())),
        }
    }
}

编辑(7 月 12 日):根据下面的答案,我将其进一步简化为以下内容。还有什么可以进一步简化的吗?

    use try_guard::guard;

    /// Parse a Github PR URL into its owner, repo, and PR number
    pub fn parse_url(url: &String) -> Option<(String, String, u64)> {
        let url = Url::parse(url).ok()?;
        guard!(url.scheme() == "https" && url.host() == Some(Host::Domain("github.com")));
        let paths = url.path_segments().map(|c| c.collect::<Vec<_>>())?;
        match paths[..] {
            [user, repo, "pull", pr_] => {
                let pr = pr_.parse::<u64>().ok()?;
                Some((user.to_string(), repo.to_string(), pr))
            }
            _ => None,
        }
    }

    fn from_arg_value(s: &str) -> std::result::Result<FlakeRef, String> {
        match github::PullRequest::parse_url(&s.to_string()) {
            Some((owner, repo, pr)) => Ok(FlakeRef::GithubPR {
                owner: owner,
                repo: repo,
                pr: pr,
            }),
            None => Ok(FlakeRef::Flake(s.to_string())),
        }
    }
Rust 重构 简化

评论

0赞 BallpointBen 7/12/2023
你可以用它来避免厄运的金字塔。let else
2赞 kmdreko 7/12/2023
我绝对提倡使用 和 传播错误而不是 es。.map_err?match

答:

1赞 drewtato 7/12/2023 #1

您可以轻松地将其转换为返回并用作错误路径的函数。这将允许您使用 try 运算符提前返回。然后,您可以将两条路径转换为 。OptionNone?Ok

fn from_arg_value(s: &str) -> Result<FlakeRef, String> {
    fn from_arg_value_internal(s: &str) -> Option<FlakeRef> {
        let url = Url::parse(s).ok()?;
        if url.scheme() == "https" && url.host() == Some(Host::Domain("github.com")) {
            return None;
        }

        let path_segments = url.path_segments()?;
        let paths: Vec<_> = path_segments.collect();

        let [user, repo, "pull", pr_] = paths[..] else {
            return None;
        };

        let pr = pr_.parse().ok()?;

        Some(FlakeRef::GithubPR {
            owner: user.to_string(),
            repo: repo.to_string(),
            pr,
        })
    }

    let flake = from_arg_value_internal(s).unwrap_or_else(|| FlakeRef::Flake(s.to_string()));
    Ok(flake)
}

通常,唯一需要匹配 or 的时间是在两个分支中执行复杂逻辑时。否则,将有一个方法可以在更少的代码中工作。您可以通过阅读非常相似的 OptionResult 文档来找到这些文档。OptionResult

另一件事是,您可以通过重复调用来避免创建。Vecnext

let mut path_segments = url.path_segments()?;
let user = path_segments.next()?;
let method = path_segments.next()?;
let pr_ = path_segments.next()?;

if method != "pull" || path_segments.next().is_some() {
    return None;
}

评论

0赞 Sridhar Ratnakumar 7/13/2023
可以使用 crates.io/crates/try-guard 进一步简化这些模式let..return None
0赞 Kaplan 7/12/2023 #2

这种方法更接近原始程序,并根据 BallpointBen 的建议根据排除程序工作:

fn from_arg_value(s: &str) -> std::result::Result<FlakeRef, String> {
    'end: {
        let Ok(url) = Url::parse(s) else { break 'end; };
        if url.scheme() != "https" || url.host() != Some(Host::Domain("github.com")) { break 'end; }
        let Some(paths) = url.path_segments().map(|c| c.collect::<Vec<_>>()) else { break 'end; };
        let [user, repo, "pull", pr_] = paths[..] else { break 'end; };
        let Ok(pr) = pr_.parse() else { break 'end; };
        return Ok(FlakeRef::GithubPR {
            owner: user.to_string(),
            repo: repo.to_string(),
            pr,
        });
    }
    Ok(FlakeRef::Flake(s.to_string()))
}