自定义类型 (Rust入门)

本文章参考自官方学习文档及社区学习文档以下是参考链接:

官方:

社区:

直接上手学习推荐:

概述点

  • 结构体
  • 枚举
  • use
  • 常量

结构体

结构体(structure,缩写成struct)有3种类型,使用struct关键字来创建:

  • 元组结构体(tuple struct),事实上就是具名元组而已
  • 经典的C语言分割结构体(C struct)
  • 单元结构体(unit struct),不带字段,在泛型中很有用

题目由通过例子学习Rust中文版(结构体)

  1. 增加一个计算长方形面积的函数 rect_area(尝试使用嵌套的解构方式)。
  2. 增加一个函数 square,接受的参数是一个 Point 和一个 f32,并返回一个 Rectangle(长方形)的信息,包括左下角的点,以及长和宽的浮点数值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#[derive(Debug)]
struct Person<'a> {
name: &'a str,
age: u8,
}

// 单元结构体
struct Nil;

// 元组结构体
struct Pair(i32, f32);

// 带有两个字段(field)的结构体
#[derive(Debug)]
struct Point {
x: f32,
y: f32,
}

// 结构体可以作为另一个结构体的字段
#[derive(Debug)]
struct Rectangle {
p1: Point,
p2: Point,
}

// 计算长方形面积使用嵌套的解构方式
fn rect_area(r: Rectangle) -> f32 {
let Rectangle{
p1: Point{x:top,y:left},
p2: Point{x:bottom,y:right},
} = r;
(top-bottom)*(right-left)
}

// 增加一个函数 square,接受的参数是一个 Point 和一个 f32,并返回一个 Rectangle(长方形),其左下角的点等于 Point 参数,长和宽都等于 f32 参数。
fn square(_p:Point, f: f32) -> Rectangle {
let rect = Rectangle {
// 结构体的实例化也是一个表达式
p1: Point{x: _p.x, y: _p.y },
p2: Point{x: _p.x-f, y: _p.y+f },
};

rect
}

fn main() {
// 使用简单的写法初始化字段,并创建结构体
let name = "Peter";
let age = 27;
let peter = Person { name, age };

// 以 Debug 方式打印结构体
println!("{:?}", peter);

// 实例化结构体 `Point`
let point: Point = Point { x: 12.2, y: 23.4 };

// 访问 point 的字段
println!("point coordinates: ({}, {})", point.x, point.y);

// 使用结构体更新语法创建新的 point,这样可以用到之前的 point 的字段
let new_point = Point { x: 0.1, ..point };

// `new_point.y` 与 `point.y` 一样,因为这个字段就是从 `point` 中来的
println!("second point: ({}, {})", new_point.x, new_point.y);

// 使用 `let` 绑定来解构 point
let Point { x: my_x, y: my_y } = point;

let _rectangle = Rectangle {
// 结构体的实例化也是一个表达式
p1: Point { x: my_y, y: my_x },
p2: point,
};

// 实例化一个单元结构体
let _nil = Nil;

// 实例化一个元组结构体
let pair = Pair(1, 0.1);

// 访问元组结构体的字段
println!("pair contains {:?} and {:?}", pair.0, pair.1);

// 解构一个元组结构体
let Pair(integer, decimal) = pair;

println!("pair contains {:?} and {:?}", integer, decimal);
println!("rect_area {:.2}", rect_area(_rectangle));
println!("square: {:?}", square(Point{x: 1.0, y: 2.0}, 3.2));
}

枚举

enum 关键字允许创建一个代表数个可能变量的数据的类型(原文:The enum keyword allows the creation of a type which may be one of a few different variants.若您对此句有 更好的翻译或理解,希望指出来,谢谢。)。在 struct 中任何合法的变量在 enum 同样是合法的。

示例出自蓝桥云课原实验楼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 该属性用于隐藏对未使用代码的警告。
#![allow(dead_code)]

// 创建一个 `enum`(枚举)来对 web 事件分类。注意变量名和类型共同指定了 `enum`
// 取值的种类:`PageLoad` 不等于 `PageUnload` `KeyPress(char)` 不等于
// `Paste(String)`。各个取值不同,互相独立。
enum WebEvent {
// 一个 `enum` 可以是单元结构体(称为 `unit-like` 或 `unit`),
PageLoad,
PageUnload,
// 或者一个元组结构体,
KeyPress(char),
Paste(String),
// 或者一个普通的结构体。
Click { x: i64, y: i64 }
}

// 此函数将一个 `WebEvent` enum 作为参数,无返回值。
fn inspect(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("page loaded"),
WebEvent::PageUnload => println!("page unloaded"),
// 从 `enum` 里解构出 `c`。
WebEvent::KeyPress(c) => println!("pressed '{}'.", c),
WebEvent::Paste(s) => println!("pasted \"{}\".", s),
// 把 `Click` 解构给 `x` and `y`
WebEvent::Click { x, y } => {
println!("clicked at x={}, y={}.", x, y);
},
}
}

fn main() {
let pressed = WebEvent::KeyPress('x');
// `to_owned()` 从一个字符串切片中创建一个具有所有权的 `String`
let pasted = WebEvent::Paste("my text".to_owned());
let click = WebEvent::Click { x: 20, y: 80 };
let load = WebEvent::PageLoad;
let unload = WebEvent::PageUnload;

inspect(pressed);
inspect(pasted);
inspect(click);
inspect(load);
inspect(unload);
}

执行结果:

1
2
3
4
5
pressed 'x'.
pasted "my text".
clicked at x=20, y=80.
page loaded
page unloaded

类型别名

若使用类型别名,则可以通过其别名引用每个枚举变量。当枚举的名称太长或者太一般化,且你想要对其重命名,那么这对你会有所帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum VeryVerboseEnumOfThingsToDoWithNumbers {
Add,
Subtract,
}

// Creates a type alias
type Operations = VeryVerboseEnumOfThingsToDoWithNumbers;

fn main() {
// We can refer to each variant via its alias, not its long and inconvenient
// name.
let x = Operations::Add;
}

最常见的情况就是在 impl 块中使用 Self 别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum VeryVerboseEnumOfThingsToDoWithNumbers {
Add,
Subtract,
}

impl VeryVerboseEnumOfThingsToDoWithNumbers {
fn run(&self, x: i32, y: i32) -> i32 {
match self {
Self::Add => x + y,
Self::Subtract => x - y,
}
}
}

该功能已在 Rust 中稳定化,可以阅读 [stabilization report][aliasreport] 来了解更多有关枚举和类型别名的知识。

使用 use

使用 use 声明的话,就可以不写出名称的完整路径了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 该属性用于隐藏对未使用代码的警告。
#![allow(dead_code)]

enum Status {
Rich,
Poor,
}

enum Work {
Civilian,
Soldier,
}

fn main() {
// 显式地 `use` 各个名称使他们直接可用,而不需要指定它们来自 `Status`。
use Status::{Poor, Rich};
// 自动地 `use` `Work` 内部的各个名称。
use Work::*;

// `Poor` 等价于 `Status::Poor`
let status = Poor;
// `Civilian` 等价于 `Work::Civilian`。
let work = Civilian;

match status {
// 注意这里没有用完整路径,因为上面显式地使用了 `use`。
Rich => println!("The rich have lots of money!"),
Poor => println!("The poor have no money..."),
}

match work {
// 再次注意到没有用完整路径。
Civilian => println!("Civilians work!"),
Soldier => println!("Soldiers fight!"),
}
}

C语言风格用法

enum也可以像C语言风格的枚举类型那样使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 该属性用于隐藏对未使用代码的警告。
#![allow(dead_code)]

// 拥有隐式辨别值(implicit discriminator,从 0 开始)的 enum
enum Number {
Zero,
One,
Two,
}

// 拥有显式辨别值(explicit discriminator)的 enum
enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff,
}

fn main() {
// `enum` 可以转成整形。
println!("zero is {}", Number::Zero as i32);
println!("one is {}", Number::One as i32);

println!("roses are #{:06x}", Color::Red as i32);
println!("violets are #{:06x}", Color::Blue as i32);
}

测试实例:链表

enum 的常见一个用法就是创建链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
use List::*;

enum List {
// Cons:元组结构体,包含链表的一个元素和一个指向下一节点的指针
Cons(u32, Box<List>),
// Nil:末结点,表明链表结束
Nil,
}

// 可以为 enum 定义方法
impl List {
// 创建一个空的 List 实例
fn new() -> List {
// `Nil` 为 `List` 类型(译注:因 `Nil` 的完整名称是 `List::Nil`)
Nil
}

// 处理一个 List,在其头部插入新元素,并返回该 List
fn prepend(self, elem: u32) -> List {
// `Cons` 同样为 List 类型
Cons(elem, Box::new(self))
}

// 返回 List 的长度
fn len(&self) -> u32 {
// 必须对 `self` 进行匹配(match),因为这个方法的行为取决于 `self` 的
// 取值种类。
// `self` 为 `&List` 类型,`*self` 为 `List` 类型,匹配一个具体的 `T`
// 类型要好过匹配引用 `&T`。
match *self {
// 不能得到 tail 的所有权,因为 `self` 是借用的;
// 因此使用一个对 tail 的引用
Cons(_, ref tail) => 1 + tail.len(),
// (递归的)基准情形(base case):一个长度为 0 的空列表
Nil => 0
}
}

// 返回列表的字符串表示(该字符串是堆分配的)
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
// `format!` 和 `print!` 类似,但返回的是一个堆分配的字符串,
// 而不是打印结果到控制台上
format!("{}, {}", head, tail.stringify())
},
Nil => {
format!("Nil")
},
}
}
}

fn main() {
// 创建一个空链表
let mut list = List::new();

// 追加一些元素
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);

// 显示链表的最后状态
println!("linked list has length: {}", list.len());
println!("{}", list.stringify());
}

注:ref 关键字可用来创建结构体/元组的字段引用,下面示例使用的是模式匹配,它还可以用于解构

原文注解:

https://doc.rust-lang.org/std/keyword.ref.html

  • 原文
    Bind by reference during pattern matching.
    ref annotates pattern bindings to make them borrow rather than move. It is not a part of the pattern as far as matching is concerned: it does not affect whether a value is matched, only how it is matched.

  • 中文
    在模式匹配期间通过引用绑定。
    ref 批注模式绑定以使其借用而不是移动。就匹配而言,它不是模式的一部分:它不会影响值是否匹配,只影响匹配方式。

&ref

  • &表示您的模式需要对对象的引用。因此&是描述对象的一部分: &FooFoo不匹配。

  • ref 表示您想要对解压值的引用。 它不匹配: Foo(ref foo)Foo(foo) 匹配相同的对象。

常量

Rust 有两种常量,可以在任意作用域声明,包括全局作用域。它们都需要显式的类型声明:

  • const:不可改变的值(通常使用这种)
  • static:具有static生命周期的,可以是可变的变量(译注:须使用 static mut 关键字)。

有个特例就是 “string” 字面量。它可以不经改动就被赋给一个 static 变量,因为它的类型标记:&’static str 就包含了所要求的生命周期 ‘static。其他的引用类型都必须特地声明,使之拥有 ‘static 生命周期。这两种引用类型的差异似乎也无关紧要,因为无论如何,static 变量都得显式地声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 全局变量是在在所有其他作用域之外声明的。
static LANGUAGE: &'static str = "Rust";
const THRESHOLD: i32 = 10;

fn is_big(n: i32) -> bool {
// 在一般函数中访问常量
n > THRESHOLD
}

fn main() {
let n = 16;

// 在 main 函数(主函数)中访问常量
println!("This is {}", LANGUAGE);
println!("The threshold is {}", THRESHOLD);
println!("{} is {}", n, if is_big(n) { "big" } else { "small" });

// 报错!不能修改一个 `const` 常量。
// THRESHOLD = 5;
// 改正 ^ 注释掉此行
}