自定义数据类型

本文中概念在Rust高手ChatGPT指导下学习

Struct 结构体

定义结构体

没有什么花里胡哨的,使用struct关键字加上结构体名称就好,字段名可以不指定(元组结构体,用括号代替大括号),字段类型必须指定(单元结构体除外,不需要括号)

⚠️注意,结构体实例化时是等效赋值语句的,所以如果使用结构体更新语法,从现有结构体实例中更新一个新的结构体,如果字段是类似String这样在堆上的数据类型,更新会等效所有权中的移动,也就是原实例失效,再使用时会报E0382部分所有权移动错误。(吐槽:Rust高手没有指出这个问题,并且在给出示例的时候信誓旦旦这样不会有问题…但是丢到rust-analyze里一下子就被指出来问题惹)

如果是更新具有copy trait的类型,而非获取所有权不影响原结构体实例。

可以通过将字段类型改为引用的方式,避免更新结构体时所有权移动的问题,但是需要定义生命周期,后续进一步学习。

结构体方法和关联函数

方法这个概念在Python这些强OOP的语言当中应该是一个比较常见的概念,这里不赘述了。除了定义在impl块内,定义方法和定义普通函数并无不同,但是方法的第一个参数为selfself:Self的简写,Self类型是impl块的类型的别名),这里self和其他变量并无不同,可以是&self也就是实例的自身引用。

和C++不同,Rust在处理对象的方法与对象引用的方法时,并无差别,因为Rust会自动解引用。

impl块中定义的函数,都被称之为关联函数,只是其中以self为第一个参数的会被称为方法。不是方法的关联函数经常被用作返回一个结构体新实例的构造函数。结构体名和 :: 语法来调用这个关联函数。

Enums 枚举

定义

使用enum关键字定义自定义类型和成员,例如:

enum IpAddrKind {
    V4,
    V6,
}

枚举值,通过let定义,而枚举成员位于其标识符的命名空间中,因此采用::表示。

关联类型

可以通过将数据类型放入枚举成员,而不是将枚举作为结构体的一部分,此时枚举成员的名字也变成了一个构建枚举的实例的函数。

    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127,0,0,1);

    let loopback = IpAddr::V6(String::from("::1"));

关联方法

与结构体相似,枚举也是使用impl来定义关联方法,不再赘述

Option 枚举

作为标准库定义的枚举,针对一个值要么有要么没有值的场景。没错,Rust中没有空值null的实现。但是没有null值并不代表没有空的概念,因此Rust通过枚举类型来解决这个问题。Option的定义如下:

enum Option<T> {
    None,
    Some(T),
}

难道这里的None不就是null换了个名字吗?不是的,因为Option<T>T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option<T>,会报E0277错误

You tried to use a type which doesn't implement some trait in a place which expected that trait.

为了更好的使用Option<T>我们就需要学习模式匹配

Match 模式匹配

通常在其他编程语言里,我们会认为Match不过是if语句换了个写法,但是在Rust里有显著的不同:

  • if表达式中条件必须是bool变量,而match可以是任意类型
  • match的分支由模式和将要运行的代码组成,使用=>分隔
  • 每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值
  • match分支可以从枚举成员中提取值,当将值与分支相比较时,可以获得枚举成员中附带的值

匹配Option

match 一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。

Rust 中的匹配是 穷尽的(exhaustive):必须穷举到最后的可能性来使代码有效。

为了使代码更加简化,Rust设计了通配模式other_,后者匹配但不获取值。同时,如果_匹配且不执行任何操作,可以使用单元值作为分支代码。

当只需要处理某一特定分支时,为了减少_ => ();这样的样本代码,可以使用if let语法糖来简化,示例如下:

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn main() {
    let coin = Coin::Penny;
    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!("State quarter from {:?}!", state);
    } else {
        count += 1;
    }
}

总结

通过这两种方法,我们可以自定义数据类型,并且能够将与数据类型关联的函数组织起来,同时保证类型安全。