- Tuple 作为函数参数时
- 结构体 Debug 信息
- 实例方法
- Automatic Referencing and Dereferencing
- Associated Functions
- Enum
- Option<T>
- match 带数据的 Enum
- if let
1. Tuple 作为函数参数时
在 Rust 中,声明一个 tuple 的时候需要写全各 component 的类型,如
fn main() { let tuple: (u32, String, f64) = (233, String::from("Hello World!"), 3.1415); println!("{}", tuple.2); }
在 tuple 作为函数参数时,也是需要完整的类型信息的
fn main() { let tuple: (u32, String, f64) = (233, String::from("Hello World!"), 3.1415); print_some_tuple(tuple); } fn print_some_tuple(t: (u32, String, f64)) { println!("{}", t.0); println!("{}", t.1); println!("{}", t.2); }
如果 tuple 的类型不匹配的话,则会报错~
![](/wp-content/uploads/2019/09/rust-tuple-type-mismatch.webp)
2. 结构体 Debug 信息
在 C/C++ 里想要 pretty print 一个结构体的话,只能自己写相应的 debug 函数,不过在 Rust 中可以只增加一个 derive 就可以让结构体在调试时有一个 pretty print。
#[derive(Debug)] struct Point { x: u32, y: u32, } fn main() { let p = Point { x: 30, y: 50 }; // print with no indent println!("p is {:?}", p); // pretty print println!("p is {:#?}", p); }
![](/wp-content/uploads/2019/09/rust-structure-pretty-print.webp)
3. 实例方法
Rust 中声明实例方法其实也挺简单的,而且相比 C++ 更方便~C++ 中默认是可以随便修改类成员的值,除非加上 const 限定;而 Rust 中则是默认为 immutable 的,想要在实例方法中修改类成员的值的话,则需要增加 mut 修饰(当然了,你的那个实例也得先是 mut 修饰的才行)~
简单来说写法模式如下
impl 结构体名字 { fn 方法名1(&self) -> 返回值类型 { // 此方法内不能修改实例成员的值 // 因为 &self 是 immutable } fn 方法名2(&mut self) -> 返回值类型 { // 此方法内可以修改实例成员的值 // 因为 &mut self 是 mutable } fn 方法名3(&self) { // 当然不论哪种方法 都可以没有返回值 } }
而且 Rust 支持同一个结构体有多个 impl,因此也很容易扩展~在调用的时候的模式如下(假设已经自己 new 了一个变量名为 instance 的实例)
let ret1 = instance.方法名1(); let ret2 = instance.方法名2(); instance.方法名3();
举一个实际的例子~
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { // cannot change the value of instance member self.width * self.height } fn grow(&mut self) { // the value of instance member is mutable self.width += 1; self.height += 1; } } fn main() { let mut rect1 = Rectangle { width: 30, height: 50 }; rect1.grow(); println!( "The area of the rectangle is {} square pixels.", rect1.area() ) }
![](/wp-content/uploads/2019/09/rust-instance-impl.webp)
4. Automatic Referencing and Dereferencing
这个则是 Rust 的另一个很方便的地方~
比如 C++ 中,我们有 .
和 ->
,.
操作符是当你直接用在实例本身上的时候,->
操作符则是当你有实例的指针的时候
而 Rust 的话,编译器会自动去做这些,我们只需要统一写 .
操作符就可以(当然要自己写的话也是可以的)
Rust 编译器会自动加上 &
, &mut
, 或者是 *
来匹配我们实际调用方法的函数签名
5. Associated Functions
在 impl 中定义一个 self 不作为第一参数(当然,self 也不能出现在别的位置上了)的函数的话,在 Rust 中被叫做 Associated Functions,其实就跟在 Python 中一样~暂且理解成类方法也是可以的~
#[derive(Debug)] struct Food { amount: u32, kind: String, } impl Food { fn consume(&mut self, eaten: u32) { self.amount -= eaten } fn eatable() -> bool { true } } fn main() { let mut beef = Food { kind: String::from("Beef"), amount: 1000 }; println!("{} is eatable? {}", beef.kind, Food::eatable()); beef.consume(20); println!("We have {} beef now", beef.amount); }
![](/wp-content/uploads/2019/09/rust-associated-functions.webp)
6. Enum
Rust 中 Enum 的特别之处,就是可以直接往里面放数据,灵活度很大(当然不放数据的话,就跟平时 C/C++ 里的 enum 差不多的用就行)
例如IP地址的话,Rust不用像 C/C++ 那样用一个/多个很复杂的结构体或者 union 去表示/判断
enum IpAddr { V4(u8, u8, u8, u8), V6(String), }
而且也并非要求 Enum 中每一个要么都不放数据,要么都放数据,比如
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
上面的在 C++ 里面的话,大概会写成下面这样
struct QuitMessage {}; struct MoveMessage { int x; int y; }; struct WriteMessage { const char * msg; }; struct ChangeColorMessage { int r; int g; int b; }; using Message = std::variant< QuitMessage, MoveMessage, WriteMessage, ChangeColorMessage >;
7. Option<T>
Rust 中与其他常见语言不一样的是,它没有 null,取而代之的是以 Option<T> 的形式(顺便一提~看到 <T> 就肯定是泛型啦,Rust 也是支持泛型的)
为什么没有 null 呢~?据「The Rust Programming Language」这本书里写到,Tony Hoare,也就是 null 的发明者,在他 2009 年的演讲「Null References: The Billion Dollar Mistake」上提到:
I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
Tony Hoare, 「Null References: The Billion Dollar Mistake」
另外 Option<T> 其实也是一个 Enum
enum Option<T> { Some(T), None }
这一点其实在某些方面跟 Swift 差不多的样子
处理 Option<T> 类型的变量的话,一般可以用 match 去做,比如
fn main() { let value1 = Some(5); let value2 = None; handle_option(value1); handle_option(value2); } fn handle_option(number: Option<i32>) { match number { Some(x) => println!("x is {}", x), None => println!("There is no number") } }
![](/wp-content/uploads/2019/09/rust-match-option.webp)
8. match 带数据的 Enum
Option<T> 作为带数据的 Enum 在上面已经见过了,那么处理自己写的带数据的 Enum 呢?
#[derive(Debug)] enum Flavour { Spicy, DoubleSpicy, ClearSoup } #[derive(Debug)] enum Food { Ramen, Hamburger, HotPot(Flavour), } fn degree_of_favorite(food: &Food) -> u32 { match food { Food::Ramen => 80, Food::Hamburger => 75, Food::HotPot(flavour) => { match flavour { Flavour::Spicy => 90, Flavour::DoubleSpicy => 100, Flavour::ClearSoup => 59 } } } } fn main() { let food = Food::HotPot(Flavour::DoubleSpicy); let degree = degree_of_favorite(&food); println!("{:?}: {:?}", food, degree); }
可以看到在处理包含了数据的 Enum 时,可以像是接受参数一样,将数据取出来,并且可以在 match 中再包含 match~
9. if let
不过你会发现,按照上面的方式的话,有时为了取一个值,就要写好几行,如果在 Option<T> 比较多的地方,代码看起来就是非常的 verbose
那么 Rust 和 Swift 一样,提供了 if let 的语法
fn some_value() -> Option<i32> { Some(233) } fn none_value() -> Option<i32> { None } fn main() { if let Some(number) = some_value() { println!("number: {}", number); } if let Some(number) = none_value() { println!("number: {}", number); } else { println!("there is no number"); } }
在拿到一个 Option<T> 的变量之后(通常是某个函数的返回值),像上面这样使用 if let,如果不是 None 的话,则该值会被 unwrap 到 number 里,然后在 if let 的 scope 里都可以使用;如果是 None 的话,则不会运行 if let 语句的 block
![](/wp-content/uploads/2019/09/rust-if-let.webp)