自由度"/>
Rust能力养成之(4):用户的自由度
前言
上一篇,介绍了Rust语言的
-
条件与决策
-
Match表达
-
循环语句
这一篇,介绍Rust语言的用户自定义数据类型
-
结构体
-
枚举
顾名思义,用户自定义类型是由用户自己参与定义,这些数据类型或者是对基本数据类型的封装(wrapper),或者是对既有用户自定义类型的进一步结合。通常有3种形式,学过C的同学想必会有印象,也就是所谓的结构体(structures->structs),枚举(enumerations->enums)和联合(unions->union)。
不可否认,这些数据类型都提升了数据的表现力。在Rust中,所提及的前两种用户自定义类型,要比C中的结构体和枚举在功能上更为强大;而在第三项---联合,与C近似,具体会在后续章节中再谈。如上所提及,本文主要介绍结构体和枚举。
结构体(Structs)
在Rust中,我们可以声明三种形式的结构体。其中最简单的是unit struct,于此可以姑且称之为单元结构,语法上是由struct关键字,对应名称和一个分号组成。以下代码示例定义了一个单元结构。
单元结构(unit struct)
// unit_struct.rs
struct DummyStruct;
fn main() {
let value = DummyStruct;
}
非常简单吧,该代码中定义了一个名为Dummy的单元结构。在main函数中,可以只使用其名称来初始化该类型。变量value现在包含了Dummy的实例,并且在占用系统资源方面是一个大小(size)为0的值。
在运行时,如果没有与之相关的数据,单元结构不会索取任何空间。就其用途而言,主要表现为3种:
-
用于对没有数据或状态关联的实体进行建模。
-
表示错误类型,因为结构本身就足以指代错误内容,已经不需要对其进行描述
-
表征状态机(state machine)实现中的状态
元组结构(tuple struct)
这里再谈一下第二种结构体,所谓的元组结构,其与数据相关联。再者,不需要对单个字段命名,可以通过在其定义中的位置来引用。比如你正在为你的图形应用程序编写一个颜色转换/计算库,并想在代码中表示RGB颜色的数值。
我们看下如下代码:
// tuple_struct.rs
struct Color(u8, u8, u8);
fn main() {
let white = Color(255, 255, 255);
// You can pull them out by index
let red = white.0;
let green = white.1;
let blue = white.2;
println!("Red value: {}", red);
println!("Green value: {}", green);
println!("Blue value: {}\n", blue);
let orange = Color(255, 165, 0);
// You can also destructure the fields directly
let Color(r, g, b) = orange;
println!("R: {}, G: {}, B: {} (orange)", r, g, b);
// Can also ignore fields while destructuring
let Color(r, _, b) = orange;
println!("R: {}, G: {}, B: {} (orange)", r, g, b);
let Color(_, _, _) = orange;
println!("R: {}, G: {}, B: {} (orange)", r, g, b);
}
可见,
-
第2行,定义Color是这里的元组结构体
-
第5行,为white变量赋值
-
第8-10行, 通过variable.
方式,访问结构体变量域内引用位置,为对应变量赋值 -
第12-14行,打印相应值
-
第16行,为orange变量赋值
-
第19-20行,用解构方式访问结构体中的个体数据,并打印结果
-
这显然是访问个体数据的有一种方式
-
-
第22-26,如果你懒得一个一个写个体参数,可以用下划线_来应付一下各自位置,相应的结果依旧可以被正确推断并打印出来。
该代码结果如下:
常规结构体(Common Struct)
对属性在四五个以内的数据进行建模时,元组结构体是一个理想的选择;一旦超过这个数据范围,就会影响到代码吗的可读性和语义推理。因此,对于具有三个以上字段的数据类型,建议使用类似c的结构体,也就是要介绍的第三种形式,同时是最常用的一种。
我们看下以下代码:
// structs.rs
struct Player {
name: String,
iq: u8,
friends: u8,
score: u16
}
fn bump_player_score(mut player: Player, score: u16) {
player.score += 120;
println!("Updated player stats:");
println!("Name: {}", player.name);
println!("IQ: {}", player.iq);
println!("Friends: {}", player.friends);
println!("Score: {}", player.score);
}
fn main() {
let name = "Alice".to_string();
let player = Player { name,
iq: 171,
friends: 134,
score: 1129 };
bump_player_score(player, 120);
}
这个代码读下来,可见,
-
第3-8行,该结构体定义方式与元组结构体相同。不同点在于是在大括号内进行较为正式的类型声明和命名
-
第10-17行,定义对结构体数据进行操作的函数,可以看到第一个参数是可变绑定,读者可以想想用意何在。
-
第20行,初始化变量name
-
第21-24行,初始化结构体变量player,仔细看下大括号内部的结构,后三个是名称+冒号+赋值,而name的写法是一种省略的方式,被称为field init shorthand
代码结果如下:
常规结构体,或者简称就是结构体,与元组结构体相比,一个优势就在于可以任意顺序对结构体对象进行赋值。此外,结构体所占的空间大小一般是所有个体属性之和,这其中会存在数据对齐的情况,除此之外,再无额外空间花销。
枚举(enums)
当需要对不同种类对象进行建模时,可以使用枚举,在定义过程中,每一种可能性或属性称为变体(variants),这些变体可以定义为是否包含数据,而所包含的数据可以是任何基本类型、结构、元组结构,甚至是枚举。
然而,如果是递归,比如有一个枚举Foo,还有一个保存Foo的变体,则该变体需要在一个指针类型之后,以避免递归无限类型定义。因为枚举也可以在栈上创建,所需要有一个预先确定的所占空间,而无限的类型定义无法在编译时确定该数值。
我们看一个例子。
// enums.rs
#[derive(Debug)]
enum Direction {
N,
E,
S,
W
}
enum PlayerAction {
Move {
direction: Direction,
speed: u8
},
Wait,
Attack(Direction)
}
fn main() {
let simulated_player_action = PlayerAction::Move {
direction: Direction::N,
speed: 2,
};
match simulated_player_action {
PlayerAction::Wait => println!("Player wants to wait"),
PlayerAction::Move { direction, speed } => {
println!("Player wants to move in direction {:?} with speed {}",
direction, speed)
}
PlayerAction::Attack(direction) => {
println!("Player wants to attack direction {:?}", direction)
}
};
}
首先请读者注意一下枚举定义的格式。再者,不难看出,这里定义了两个枚举,Direction 和 PlayerAction,基于此,可以创建实例,这些在主函数中已有体现,比如第21-23行
注意,枚举必须要初始化的,但是选择任意一个变体就可以。根据给定的枚举值,进行匹配并执行相应结果,则是第25-34行的内容,不难看到其中的三种行为,Wait,Move和Attack。
最后,看下第3行的 #[derive(Debug)],该属性为了保证println!()使用{:?}格式字符串,是由Debug的特性生成方法来实现的。
代码结果如下:
结语
从函数式编程的角度来看,结构体和枚举也被称为代数数据类型(Algebraic Data Types ,ADTs),因为其可表示值的可能范围可以使用代数规则表示。比如加法和乘法的枚举是各自变体运算值的范围。后续还会对此再讨论一下。
本篇介绍了两种用户自定义数据类型:结构体和枚举,其中已经体现了Rust语言的一部分设计重点。下一篇,会讲一下与类型相关的函数功能与方法。
主要参考和建议读者进一步阅读的文献
1.Rust编程之道,2019, 张汉东
2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger
3.Hands-On Data Structures and Algorithms with
4.Rust,2018,Claus Matzinger
5.Beginning Rust ,2018,Carlo Milanesi
6.Rust Cookbook,2017,Vigneshwer Dhinakaran
更多推荐
Rust能力养成之(4):用户的自由度
发布评论