Rust中非返回Result场景下?操作符的合理使用方法问询
如何在非Result返回的函数中处理Result时避免过度缩进?
我非常认可Rust的类型系统,但处理Result类型时,很容易遇到过度缩进的问题。通常用map_err配合?操作符能解决,但这个方案只适用于函数返回Result的场景。如果函数错误处理完后不需要返回Result,问题就来了。
比如下面这段代码就无法运行:
/// Returns an HTML string with the published blogs first then the unpublished ones fn edit_admin_page_filled() -> String { use database::blog_posts::*; // error_to_page returns an Html string from an error let published_blog_posts: Vec<_> = get_published_blog_posts().map_err(error_to_page)?; let unpublished_blogposts = get_unpublished_blog_posts().map_err(error_to_page)?; admin_edit(published_blog_posts, unpublished_blogposts) }
原因很明显:函数返回的是String,但?会尝试返回Err(String),类型不匹配。unwrap_or_else也解决不了这个问题。
我试过用闭包来避免缩进,虽然能正常编译,但写法太繁琐:
(|| -> Result<String, String> { let published = get_published_blog_posts().map_err(error_to_page)?; let unpublished = get_unpublished_blog_posts().map_err(error_to_page)?; Ok(admin_edit(published, unpublished)) })().unwrap_or_else(|e| e)
另外还有两个备选方案,但都有缺陷:
- 用
if_chain这类crate或者if let chains:只能处理Ok分支,没法通过类型系统优雅处理else分支; - 单独检查错误后用
unwrap():可行但必须用unwrap(),除非能百分百保证不会出现Err,否则我不想用这个方法:
let published_blog_posts = get_published_blog_posts().map_err(error_to_page); let unpublished_blogposts = get_unpublished_blog_posts().map_err(error_to_page); if let Err(e) = published_blog_posts { return e; } if let Err(e) = unpublished_blogposts { return e; } admin_edit(published_blog_posts.unwrap(), unpublished_blogposts.unwrap())
推荐解决方案
1. 自定义宏(最简洁的稳定方案)
写一个简单的宏,模拟?的逻辑,但直接在错误时返回函数的目标类型:
macro_rules! try_return { ($expr:expr) => { match $expr { Ok(val) => val, Err(e) => return e, } }; }
然后在函数里直接用这个宏替代?:
fn edit_admin_page_filled() -> String { use database::blog_posts::*; let published = try_return!(get_published_blog_posts().map_err(error_to_page)); let unpublished = try_return!(get_unpublished_blog_posts().map_err(error_to_page)); admin_edit(published, unpublished) }
这个宏会自动将Result拆包:如果是Ok就取出值继续执行,如果是Err就直接返回错误值,完全没有多余缩进,也不用依赖任何外部crate。
2. Nightly版的Try Blocks(不稳定)
如果你能用Rust Nightly版本,可以用Try Blocks特性,它允许在非Result返回的函数里直接用?,写法比闭包简洁:
#![feature(try_blocks)] fn edit_admin_page_filled() -> String { use database::blog_posts::*; let result: Result<String, String> = try { let published = get_published_blog_posts().map_err(error_to_page)?; let unpublished = get_unpublished_blog_posts().map_err(error_to_page)?; admin_edit(published, unpublished) }; result.unwrap_or_else(|e| e) }
不过这个特性目前还未稳定,不适合生产环境使用。
总结
自定义宏是目前稳定版Rust中最优雅的解决方案,既保留了?操作符的简洁性,又能在非Result返回的函数中处理错误,完全避免过度缩进,也不用被迫使用unwrap()。
内容的提问来源于stack exchange,提问作者Kosmas Xenakis
相关产品推荐
相关产品推荐

