rustlings 学习笔记

为了在 rustlings 里面用 rust analyzer,可以运行 rustlings lsp 命令,这样会生成 rust-project.json 文件提供一些 crate 信息,lsp 就可以使用了~

这里记录一下写的不是很优雅的地方可以怎么改进,以及一些理解。

while let 很适合用于完成遍历数组的工作,比如

rust


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”

c

while(p) {
	...
}

为了从形如 "Gerald,70" 的字符串中提取出名字与年龄,需要做的判断包括,字符串非空,逗号前后都要有内容,否则返回 Person::default(),如果挨个用 if 判断的方式是这样:

rust

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 判断) 来过滤分支。

rust

	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

rust

if s == "" {
可以优化成
if s.is_empyty() {

所以总体可以写成这个样子,不过这么写的不是很好的地方是就是不好体现错误信息了…

rust

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() 来返回默认值。

rust

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()
    }
}

如果是出需要返回不通的错误值的时候,组合器很好用的。

rust

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 的作用

rust

let a = match func() {
    Ok(v) => Some(v),
    Err(_) => None,
};
let b = if let Err(v) = func() {
    Some(v)
} else {
    None
};

相应的 clippy 建议的写法是:

rust

let a = func().ok();
let b = func().err();

双向链表并不是使用 Box 而是使用了 NonNull 来包裹,是因为 Box 的所有权是独占的,无法直接表示共享所有权或循环结构(如双向链表)。而且 nonnull 性能好点,。

rust

struct Node<T> {
    val: T,
    next: Option<NonNull<Node<T>>>,
}

堆排序中,从 idx=1 位置取出一个元素并用末尾的元素替换 idx 1 的元素,这个操作可以用 swap_remove 完成

rust

    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)
    }

可以用 get mut 来修改

rust

	if let Some(v) = self.adjacency_table_mutable().get_mut(p) {
		v.push((q.to_string(), e))
	}

也可以用 entry and_modify。emmmm 不过这两种,好像差不多?

rust

	self.adjacency_table
		.entry(edge.1.to_owned())
		.and_modify(|edges| edges.push((edge.0.to_owned(), edge.2)));

在写的时候感觉 some count 这些需要特地换成 iter 很不习惯,想想好像也合适,毕竟是循环…