代码杂谈:从一道面试题看学会Rust的难度

参考资料(Web3/DAOs/Metaverse的政治经济哲学等相关理论研究

输出文章Think about Metaverse 01)Loot范式的双重结构Think about Crypto 2)加密运动的社区文化与正统性意义,建设Web3的精神信仰03)Think about Web3 : Web3的阴暗面,权力贿赂/产权欺诈/剥…

一、面试题
最近看到一道面试题,面试者没有明确应试者的答题时间及完成目标,只要求至少编译能通过。

fn to_string(v: i32) -> Result<String, i32> {    match v {        0.. => Ok(v.to_string()),        _ => Err(v)    }}
fn main() -> Result<(), String> { let o1 = Some(1); let o2: Option<String> = o1.map(|v| to_string(v))?; println!("option value is: {:?}", o2); let arr1 = [1,2,3,4,5]; let arr2: Vec<String> = arr1.iter().map(|v| to_string(*v)).collect()?; println!("array value is: {:?}", arr2); Ok(())}

也可使用下面链接进行线上编译试验。。
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=34c54a9f1f53dba34d2b8223d9fdcc4a

这里尝试分析如何解答既能让面试者可考察应试者编写Rust代码的能力,又能让应试者如何面对编写Rust代码时遇到的一些困难。

二、编译分析试题
1.直接编译,编译器会提示如下错误:

error[E0308]: `?` operator has incompatible types  --> src/lib.rs:10:30   |10 |     let o2: Option<String> = o1.map(|v| to_string(v))?;   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found enum `Result`   |   = note: `?` operator cannot convert from `Result<String, i32>` to `Option<String>`   = note: expected enum `Option<String>`              found enum `Result<String, i32>`
error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result` --> src/lib.rs:10:54 |8 | / fn main() -> Result<(), String> {9 | | let o1 = Some(1);10 | | let o2: Option<String> = o1.map(|v| to_string(v))?; | | ^ use `.ok_or(...)?` to provide an error compatible with `Result<(), String>`11 | | println!("option value is: {:?}", o2);... |17 | | Ok(())18 | | } | |_- this function returns a `Result`

2.分析错误E0308,E0277
E0308提示的是?操作符的输入参数的类型Result<String, i32>与期望的参数类型Option<String>不匹配;
E0277提示的是?操作符只能用在Result类型,而不能是Option类型;

3.尝试按编译器提示进行修改
初看下来,是使用?操作符带来的问题,尝试按错误提示在第10行加上ok_or试试看

let o2: Option<String> = o1.map(|v| to_string(v)).ok_or("")?;

这时会少了E0277的错误,但E0308错误还是存在;

怎么办?如果这时没有IDE提示或API手册可快速查询的话,就会非常的困难。
因为提示的错误越多说明可供选择的修改路径就越多,选择越多反而感觉无从下手;

4.根据IDE提示和查询API手册进行分析
其实o1.map(|v| to_string(v))的返回类型不是Result<String, i32>,而是Option<Result<String, i32>>,后面再加上?操作符,才有E0277的提示,表示Option类型不能进行?操作;

到此,可知现在问题变成由Option<Result<String, i32>>类型数据经过?操作后,期望返回Option<String>类型值;

5.尝试进行类型转换
简单看下来,直接将Option中的Result<String, i32>通过unwrap相关方法可转换成String,应该最方便;

let o2: Option<String> = o1.map(|v| to_string(v).unwrap_or("".to_string()))?;

但这时还会提示E0308,提示?操作符不能将类型String转换成Option<String>;
这个提示有些云里雾里,根据前面类型转换的分析,o1.map(|v| to_string(v).unwrap_or(“”.to_string()))返回值类型已经是Option<String>,无须再进行?操作,直接去掉?,第10行即可通过编译;

let o2: Option<String> = o1.map(|v| to_string(v).unwrap_or("".to_string()));

6.更符合出题者预期的解决方式
虽然按照上面类型转换的方式并去掉?操作符,能编译通过,但出题者为啥需要这个?操作符呢;

如果对?操作符功能有最基本的了解的话,可知它用来将当前函数可能发生的错误直接返回抛出到上层函数调用者;

而我们使用了unwrap_or()实际是将当前函数的错误给屏蔽掉,并强制转换成空,这某种程度上可能违反函数设计者及实现者的初衷(虽然面试者没有明确提到这一点),如果我们要符合预期将错误抛出,哪怎么办?

回到分析4我们知道需要将Option<Result<String, i32>>类型数据经过?操作后,期望返回Option<String>类型值并能抛出错误。

但是期望通过?操作符可返回Option<String>类型值并能抛出错误,往往需在类型Result<Option<String>, String>数据执行?操作。

这样就变成一个如何将类型Option<Result<String, i32>>转换成Result<Option<String>, String>类型的问题。

这时如果没有API查询手册或不熟悉Option API的话,可能就有点束手无策啦;
但实际上Option有这样一个transpose接口可以将Option<Result<String, i32>>,转换成Result<Option<String>,i32>;

但是直接在Result<Option<String>,i32>类型上执行?操作,可行嘛?

error[E0277]: `?` couldn't convert the error to `String`  --> src/lib.rs:10:66   |8  | fn main() -> Result<(), String> {   |              ------------------ expected `String` because of this9  |     let o1 = Some(-1);10 |     let o2: Option<String> = o1.map(|v| to_string(v)).transpose()?;   |                                                                  ^ the trait `From<i32>` is not implemented for `String`   |   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait


因为上层函数main的返回类型为Result<(),String>,?操作会隐式的对可能发生的错误类型进行转换即尝试从i32类型转换成String类型(
也许这是出题者故意留下的坑);

需要显式的手工将Result<Option<String>, i32>转换成Result<Option<String>,String>,就可正常编译通过并更优雅的解决使用?进行错误抛出的情况;

let o2 :Option<String> = o1.map(|v| to_string(v)).transpose().map_err(|i| i.to_string())?;


7.解决下一个错误
在经过分析5或6进行修改之后,继续编译会发现下一个错误;

error[E0282]: type annotations needed  --> src/lib.rs:14:73   |14 |     let arr2: Vec<String> = arr1.iter().map(|v| to_string(*v)).collect()?;   |                                                                         ^ cannot infer type


E0282提示编译器无法推导出类型,需要一个明确的类型标识;

初看起来,也会一头雾水,怎么需要编译器去推导类型

结合前面to_string(*v)会返回类型Result<String,i32>,说明mapiterator::Item的类型为Result<String,i32>,继续进行collect();

简单的看来,返回的类型应该Vec<Result<String,i32>>,现在期望的返回Vec<String>用上?操作

如果简单的为了编译通过,可以采用类似分析5的方式,通过unwrap相关方法直接丢弃错误Resut<String,i32>转换成String,同时去掉?操作

let arr2: Vec<String> = arr1.iter().map(|v| to_string(*v).unwrap_or("".to_string())).collect();

8.更符合出题者预期更优雅的解决方式
根据分析7的描述,能不能有既能返回Vec<String>,又能抛出错误的解决方式呢;

这涉及一个通用的逻辑,如何将一组Result<T, E>元素,收集成Vec<T>或一旦其中有一个是E,直接返回E的问题;

标准库中通过定制化类型Result<T,E>提供FromIterator Trait的实现可以实现上面的逻辑(如果原来不知道这种方式的话,哪可能就没有办法啦),下面的示例代码就展示这种逻辑;

let v = [Ok(2), Ok(4), Err("err!"), Ok(8)];let res: Result<Vec<_>, &str> = v.into_iter().collect();assert_eq!(res, Err("err!"));let v = [Ok(2), Ok(4), Ok(8)];let res: Result<Vec<_>, &str> = v.into_iter().collect();assert_eq!(res, Ok(vec![2, 4, 8]));

知道有上面的转换逻辑可使用,但要求明确指定collect返回的类型为Result<Vec<String>,i32>,这往往在编译器推导时无法确定逻辑的情况需要的,否则会提示;

转换成Result<Vec<String>, i32>之后,还需进行一次i32到String的错误类型转换,这样?操作符就可以直接返回;

let arr2: Vec<String> = arr1.iter().map(|v| to_string(*v)).collect::<Result<Vec<String>, i32>>().map_err(|i| i.to_string())?;

修改后版本,详见下面链接,可进行编译试验。。
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5ed731ac52901c36ca6a8c4f5f4e7143

三、总结
1.通过上面的分析与试验,要编写一段优雅的Rust代码,确实比较难,特别是没有IDE提示或API查询可用的情况下;

2.编译器所提示的错误及建议未必完全靠谱,存在提示模糊的地方;
它往往只是在编译器看来,可满足其编译通过的一些选项,这些选项未必是开发者真实期望想表达的语义;

3.Rust属于静态语言,要求严格类型匹配,虽提供了一些可进行类型推导的场景,但有时不够灵活,需要明确指定参数或调用指定接口进行转换

这往往由其Unification、Trait Select、类型推导算法导致的,特别是在泛化/泛型广泛组合使用的情况下;

如对上面相关算法感兴趣的话,请帮忙多点点赞,以便更有动力将这些算法总结总结分享给大家,有利于编写和理解Rust代码;

盘点 31 个值得关注的未发币新公链项目

附操作教程,埋伏一波。原文标题:《32 个未发币新公链项目整理(附教程)》撰文:追风 Lab目前各种公链雨后春笋般涌出,其中最有代表性的还数 Aptos、Sui、Linera 等 Meta 系。今天整理了 31 个未发币公链项目(附教程),有兴趣收藏、关注。1…

Click to rate this post!
[Total: 0 Average: 0]

人已赞赏
小白百科每日优选

AMM 进化史

2022-9-1 12:47:41

名家说每日优选

加密研究框架

2022-9-1 12:51:25

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索