从零开始的 Rust 学习笔记(4)

  1. Tuple 作为函数参数时
  2. 结构体 Debug 信息
  3. 实例方法
  4. Automatic Referencing and Dereferencing
  5. Associated Functions
  6. Enum
  7. Option<T>
  8. match 带数据的 Enum
  9. 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 的类型不匹配的话,则会报错~

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

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

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

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

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

声明: 本文为0xBBC原创, 转载注明出处喵~

Leave a Reply

Your email address will not be published. Required fields are marked *