rust学习备忘

常用库

参考 rust 圣经文档:日常开发三方库精选

基础

  • char 是 4 字节,区别于 Java 的 2 字节,可以表示一个 Unicode 字符
  • 元组可以解构,方便的赋值给多个变量
1
2
3
let tuple = (1, "hello", 4.5, true);

let (a, b, c, d) = tuple;
  • rust 的 match 和 java 的 case switch 作用是一样的,不过模式匹配的能力更加强大,可以匹配枚举项的入参。下面实例是只有当枚举入参第三个值是 1 的时候将枚举第一个值打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Season {
SPRING(u8, u8, u8),
SUMMER(u8, u8, u8),
AUTUMN(u8, u8, u8),
WINTER(u8, u8, u8),
}

fn main() {
let my_season:Season = Season::SPRING(9, 9, 1);
match my_season {Season::SPRING(a, b, 1)=>println!("a is {} while c=1", a),
_ => {},
}

}
  • rust 是以表达式为主的一门语言,需要区分语句和表达式。语句是以分号结尾,不会有返回值;表达式没有结束符,表达式结果作为返回值。下面示例 myfun 用分号结尾,则最后返回默认的空对象 unit
  • 没有 java 的基础类型拆装箱问题,语言一开始就设计考虑了泛型,是编译时的泛型处理,因此不像 java 为了兼容类型擦除的泛型实现而需要引入基础类型的包装类型,后面还得搞 valhalla 这种项目
  • 相比 java 对闭包支持比更好,更符合函数式语言,返回结果直接返回(a,b)这样的匿名 struct 都是可以的
  • 相比 java 是一种更加轻量、灵活的 OOP 语言。rust 设计之初即考虑很多时候我们并不需要继承父类所有的东西,使用 trait+impl 更加灵活。需要施加实现约束和附加特性通过 trait 即可,是一种独特的多态方式: bounded parametric polymorphism
  • 结构体不使用其他数据的引用,这样确保其所有权。如果需要存储别的数据的引用需要显式定义生命周期确保结构体引用的数据有效性和结构体保持一致。
  • 区别方法和函数,方法可以用.访问,函数用::访问
  • 使用 Optional处理空值,没有 null
  • 提供宏的能力,相比函数宏可以在编译时确定;也可以用于实现类型上的 trait
  • rust 没有定义异常,如果是不可恢复错误就是 panic,否则封装成一个 Optional 或者 Result 对象,用好?符号传播错误

所有权

所有权是 rust 的重要特性,使得其无需垃圾回收也可以做到内存安全。理解的核心在于:在编译器通过一套基于规则的所有权体系提前避免内存问题。

  • 堆上的对象的赋值区注意别于浅拷贝,称之为 move,相比浅拷贝额外多了使之前的对象无效化,如下代码,s1 赋值给 s2 后就无效了,称之为 moved。这个是为了保证所有权系统在调用 drop 清理内存的时候发生重复回收。
  • 栈上分配的基础类型都有实现 Copy trait,在栈上开辟内存块比堆上快很多。Copy trait 和负责自动内存清理的 Drop trait 是互斥的,需要留意
  • 方法中的对象返回的话,会转移所有权,作用域不算结束,也不会调用 drop
  • 函数每次都返回来转移所有权比较啰嗦,可以直接使用“引用”做为入参,这样省略了原来对象赋值时转移所有权那套流程,称之为借用。函数处理对象的时候并没有发生所有权的转移。
  • 语言层面禁止同一时间处理超过 1 个的可变引用,从语言层面避免数据竞争。一个函数中需要同一对象的多个引用是可以的,只是不能同时拥有。通过{}显式定义作用域,让第一次可变引用结束作用域之后就可以再创建针对这个对象新的可变引用。不可变引用和可变引用也无法同时存在。
  • rust 语言层面避免产生了 dangling pointer。当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。如下代码编译就不会通过。因为按照代码 s 离开作用域要被 drop,但是又返回了作用域结束的对象的引用,这个有点像 java 的对象逃逸。如果这样的代码编译器不处理,外部方法就可能处理一个 dangling pointer 引发内存访问问题。下面代码最后一行改成返回 s 字符串即可,这样所有权没有被释放,就不会 drop
1
2
3
4
5
6
7
fn dangle() -> &String { // dangle 返回一个字符串的引用

let s = String::from("hello"); // s 是一个新字符串

&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
// 危险!
  • 可以使用 slice 保存字符串部分内容的引用(类型是 &str 指向二进制程序特定的一个位置)。函数使用&str 可以同时接受 String 和 String 的 slice

集和

  • 注意 vec 的使用,区别&[]和.get 调用。如果越界需要触发 panic 可以使用&[], .get 会返回一个 Optional<&T>

trait 和生命周期

  • 两个 crate 不可能对相同类型实现相同的 trait,rust 要求只有当至少一个 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait。
  • 多个 triat 建议的语法:
1
2
3
4
5
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{

  • 生命周期帮助 rust 编译器确定导致作用域使用哪一个参数的,例如如下例子。指定生命周期注解后,会使用相同生命周期注解中 作用域更短的那一个作为编译器使用的生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";

let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

  • 编译器计算生命周期有三条规则,具体看下官方文档,如果计算不出来所有参数的生命周期就会报编译错误