自定义数据类型
本文中概念在Rust高手ChatGPT指导下学习
Struct 结构体
定义结构体
没有什么花里胡哨的,使用struct
关键字加上结构体名称就好,字段名可以不指定(元组结构体,用括号代替大括号),字段类型必须指定(单元结构体除外,不需要括号)
⚠️注意,结构体实例化时是等效赋值语句的,所以如果使用结构体更新语法,从现有结构体实例中更新一个新的结构体,如果字段是类似String
这样在堆上的数据类型,更新会等效所有权中的移动,也就是原实例失效,再使用时会报E0382部分所有权移动错误。(吐槽:Rust高手没有指出这个问题,并且在给出示例的时候信誓旦旦这样不会有问题…但是丢到rust-analyze里一下子就被指出来问题惹)
如果是更新具有copy
trait的类型,而非获取所有权不影响原结构体实例。
可以通过将字段类型改为引用的方式,避免更新结构体时所有权移动的问题,但是需要定义生命周期,后续进一步学习。
结构体方法和关联函数
方法这个概念在Python这些强OOP的语言当中应该是一个比较常见的概念,这里不赘述了。除了定义在impl
块内,定义方法和定义普通函数并无不同,但是方法的第一个参数为self
(self: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;
}
}
总结
通过这两种方法,我们可以自定义数据类型,并且能够将与数据类型关联的函数组织起来,同时保证类型安全。