也不知道是第几次重启 Rustlings 了,这一百道题从上半年通关到暑假又通了一次又到现在又打开,每一次打开都是不一样的陌生(好。)。
说起 Rust,抛开它的所有权机制不说,它还是一个整体上挺直观的语言。这里的直观是说语言简洁,没有什么花里胡哨的东西,语法糖也很符合直觉。那不直观的地方呢?
字符串#
就拿下面这个例子:
对于双引号包起来的这种内容 "blue"
,在 Rust 中叫字符串字面量,有点像常量的这种概念。而字符串指的是一个 Rust 中的数据类型 str
。而切片通常以引用形式出现 &str
,像字面量,就是储存在程序的二进制输出中,所以也是一种引用(强行理解中……)。而要根据字面量构造字符串,可以使用 String::from("literal")
或者 "literal".to_string()
。
Daniel 2018-03-20 18:15
from 和 into 是一对,实现了 From trait 就会自动反过来实现 Into,实现都是调用的 `str.to_owned`, `to_string` 调用的 `String::from`
所以背后都是调用的 `to_owned`……
这么设计的原因还是一致性,也就是所有权的体现。比如这个例子:
word 的所有权从 String::from()
开始,然后被转移到 if - else 分支中结束。如果没有默认引用的话,这里就会造成很强的不一致,想象借用一个字面量?这本身很奇怪。
像是 trim()
,replace()
这种直接对字符串本身进行操作的函数,输入 &str
,返回也是 &str
。而如果要对字符串进行拼接等,这种在其他语言里需要多一步赋值的操作,就需要先对左操作数转换类型(制造所有权)然后再把 &str
类型贴上去了。
字典(哈希表)#
丑啊,是真的丑啊。这个 unwrap()
是怎么来的呢?简单而言,这玩意是一个隐式错误处理的方法,很直观地理解就是哈希表这玩意有可能找不到,所以在找不到的时候应该及时扔出一个错误表示异常,所以 get_mut()
之后还需要加一层 unwrap()
。
但真相是什么呢,这样写应该是不 Rust 的一种写法,倒比较像是普通语言会写的样子。通常,用 get()
直接拿值,要修改值时,要么 insert()
全部替换了,要么使用 entry().or_insert()
方法。
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
错误处理#
Rust 的错误处理大概分成两种。
第一种是 panic
,字面意思,就是崩溃。打印一条错误信息,然后崩溃(不太严谨?按照 Rust by Example 是说“回退任务,退出程序”)。就相当于是写其他语言的时候遇到错误给直接 exit 了那种感觉,比较暴力,所以就是在测试的时候使用。
第二种,Option
和 Result
归在一起。前者是一个枚举类型 Option<T>
,可以是 Some(T)
也可以是 None
,通过 match 或者 if-let 可以很好处理各种情况。或者通过前面说到的 unwrap
隐式处理,但因为如果是 None 就会 panic,所以这种隐式处理要求 Option
中一定有值;或者接着用 expect
继续输出一些错误信息。后者进一步细化 Option<T>
,对于一些并不是 None 的输入,使用 Result<T, E>
包装返回的错误或者操作成功的值,T 代表了操作成功值的类型,而 E 是错误类型(或者用来解释错误的值类型,字符串之类的)。
用 ?
可以强行不处理 panic 的情况
不是不得不提一句 Rust by Example 的中文翻译是不是机翻啊??
这种让 fn main() -> Result<(), ErrorType>
的做法就挺像把错误码封装起来的。
实现解析错误的方法并返回对应的错误。
泛型#
为什么 impl 后面也需要 <T>
?
Trait#
实现 Trait 要修改自己的时候记得加 mut 使其可变。
害面向对象。
不是,这有点逆天了吧!
生命周期#
生命周期主要是用来避免悬垂引用的,读到一片野指针指向的区域显然不是什么好事。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
对于生命周期,Rust 有一套标注语法。这套语法中在引用 &
后面加上 '
和其名称,一般要方便就直接写 'a
。可以结合 mut
形成带有生命周期标注的引用。标注就意味着带有同一个名称标注的引用应该存在的时间一样长,或者说有一个最晚的生存期限(?)。
对于上面这个例子,其实加了标注并不改变它的生命周期,而只是告诉编译器他们俩是“一类”生命周期的引用。这样借用检查器对于在处理调用这个函数的位置的时候就可以计算是否出现引用比数据生命周期更长(更晚)的情况了。
所以同理,对于使用引用类型的结构体也要做类似的处理。
测试#
喜欢喜欢喜欢。
迭代器#
Rust 中遍历数组之类的方法实际上是用迭代器迭代的语法糖。而这么一个有所有权的语言,被迭代器访问过后实际上就会消费掉这个 item 并返回 Some(item)
。所以要实现一个转大写的函数,只需要:
Rust 自己也实现了不少迭代器的方法,这个 fold 就很有意思。
fold 传入一个 acc 初始值和一个闭包,闭包有两个参数,一个是 acc,另一个就是获取的元素,然后实现闭包的方法返回下一次 acc 应该有的值。
智能指针#
感觉有点像 Linux 文件系统的硬链接?