rustlings 学习笔记
为了在 rustlings 里面用 rust analyzer,可以运行 rustlings lsp
命令,这样会生成 rust-project.json
文件提供一些 crate 信息,lsp 就可以使用了~
这里记录一下写的不是很优雅的地方可以怎么改进,以及一些理解。
while let 用来遍历链表非常合适
while let 很适合用于完成遍历数组的工作,比如
while let (Some(node_a), Some(node_b)) = (current_a, current_b) {
let val_a = unsafe { &(*node_a.as_ptr()).val };
let val_b = unsafe { &(*node_b.as_ptr()).val };
...
相比起 cpp 可能会用到的写法,含义上清晰许多呢,while let 就暗含了“只遍历 Some 的情况,不包括 None”
while(p) {
...
}
match 可以用来做一些格式的匹配
为了从形如 "Gerald,70"
的字符串中提取出名字与年龄,需要做的判断包括,字符串非空,逗号前后都要有内容,否则返回 Person::default()
,如果挨个用 if 判断的方式是这样:
impl From<&str> for Person {
fn from(s: &str) -> Person {
// 检查非空
if s.len() == 0 {
return Person::default();
}
let sv: Vec<_> = s.split(",").collect();
// 只有一个 ","
if sv.len() != 2 {
return Person::default();
}
let number = sv[1].parse::<usize>();
if sv[0] == "" || number.is_err() {
Person::default()
} else {
Person {
name: sv[0].to_string(),
age: number.unwrap(),
}
}
}
}
可以用 match 来完成这些工作,还可以加上卫语句(if 判断) 来过滤分支。
match s.split(",").collect::<Vec<&str>>().as_slice() {
&[name, age] if !name.is_empty() && !age.is_empty() => {},
_ => return Person::default(),
}
此外这里有一个 clippy 提示,is_empyty() 的比较效率更高。Clippy Lints
if s == "" {
可以优化成
if s.is_empyty() {
所以总体可以写成这个样子,不过这么写的不是很好的地方是就是不好体现错误信息了…
impl From<&str> for Person {
fn from(s: &str) -> Person {
// as_slice 返回 &[&str] 切片的引用
match s.split_once(',') {
Some((name, age)) if !name.is_empty() && !age.is_empty() => {
if let Ok(number) = age.parse::<usize>() {
Person {
name: name.to_string(),
age: number,
}
} else {
Person::default()
}
}
_ => Person::default(),
}
}
}
除了 filter 也可以过滤一些 option,unwrap_or_default()
来返回默认值。
impl From<&str> for Person {
fn from(s: &str) -> Person {
s.split_once(',')
.filter(|(name, age)| !name.is_empty() && !age.is_empty())
.and_then(|(name, age)| age.parse::<usize>().ok().map(|age_num| (name, age_num)))
.map(|(name, age_num)| Person {
name: name.to_string(),
age: age_num,
})
.unwrap_or_default()
}
}
错误处理的时候组合器很好用
如果是出需要返回不通的错误值的时候,组合器很好用的。
impl FromStr for Person {
type Err = ParsePersonError;
fn from_str(s: &str) -> Result<Person, Self::Err> {
if s.is_empty() {
return Err(ParsePersonError::Empty);
}
match s.split(",").collect::<Vec<_>>().as_slice() {
&[name, age] if name.is_empty() => Err(ParsePersonError::NoName),
&[name, age] => age
.parse::<usize>()
.map(|number| Person {
name: name.to_string(),
age: number,
})
.map_err(|e| ParsePersonError::ParseInt(e)),
_ => Err(ParsePersonError::BadLen),
}
}
}
要去理解这些组合器是什么意思可以去看 clippy 里面的 manual_xxx 的部分,比如 ok 和 errr
clippy 提醒需要避免的写法是,实际上也是解释了 ok 和 err 的作用
let a = match func() {
Ok(v) => Some(v),
Err(_) => None,
};
let b = if let Err(v) = func() {
Some(v)
} else {
None
};
相应的 clippy 建议的写法是:
let a = func().ok();
let b = func().err();
NonNull
双向链表并不是使用 Box 而是使用了 NonNull 来包裹,是因为 Box
的所有权是独占的,无法直接表示共享所有权或循环结构(如双向链表)。而且 nonnull 性能好点,。
struct Node<T> {
val: T,
next: Option<NonNull<Node<T>>>,
}
还有一些 vec 的方法
堆排序中,从 idx=1 位置取出一个元素并用末尾的元素替换 idx 1 的元素,这个操作可以用 swap_remove
完成
fn next(&mut self) -> Option<T> {
if self.is_empty() {
return None;
}
// 最后一个元素移动到 idx 1 位置
let result = self.items.swap_remove(1);
self.count -= 1;
let mut idx = 1;
while self.children_present(idx) {
....
}
Some(result)
}
关于修改 hashmap 里面的元素
可以用 get mut 来修改
if let Some(v) = self.adjacency_table_mutable().get_mut(p) {
v.push((q.to_string(), e))
}
也可以用 entry and_modify。emmmm 不过这两种,好像差不多?
self.adjacency_table
.entry(edge.1.to_owned())
.and_modify(|edges| edges.push((edge.0.to_owned(), edge.2)));
循环的事情交给 iter
在写的时候感觉 some count 这些需要特地换成 iter 很不习惯,想想好像也合适,毕竟是循环…