1. 简介
在 Rust 中,每个值都属于某一个数据类型,用来告诉 Rust 它被指定为何种数据,以便明确数据处理方式。Rust 基本数据类型主要有两类子集:标量(scalar)和复合(compound)。
- 此文所讲的基本数据类型都是 Rust 原生的数据类型,它们都是创建在「栈」上的数据结构。
- Rust 标准库还提供了一些更复杂的数据类型,它们有些是创建在「堆」上的数据结构,比如下文提到的
vector
数据类型。
【注】Rust 是静态类型语言,因此在编译时就必须知道所有变量的类型。通常,根据值及其使用方式,Rust 编译器可以推断出我们想要用的类型;当多种类型均有可能时,必须增加类型注解,否则编译会报错。
2. 标量类型
标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。
2.1 整型
Rust 内建的整数类型如下表所示:
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
其中,arch 长度依赖于运行程序的计算机架构:64 位架构上为 64-bit,32 位架构上为 32-bit。
Rust 中书写数字字面值的形式如下表所示:
数字字面值 | 举例 |
---|---|
Decimal(十进制) | 98_222 |
Hex(十六进制) | 0xff |
Octal(八进制) | 0o77 |
Binary(二进制) | 0b1111_0000 |
Byte(单字节字符) | b'A' |
其中,Byte 的书写形式仅限于 u8
类型,R_
为分隔符以方便读数。
【注】Rust 的默认整型为 i32
,它通常是最快的。
整型溢出
- 在 debug 模式下编译时,Rust 检查这类问题并使程序 panic,即表示程序因错误而退出。
- 在 release 模式下编译时,Rust 不检测溢出,而是会进行一种被称为二进制补码包装的操作(本质就是忽略溢出的位)。
2.2 浮点型
Rust 有两个原生的浮点数类型:f32
和 f64
,默认浮点数类型是 f64
。浮点数采用 IEEE-754 标准表示,法2
是单精度浮点数,f64
是双精度浮点数。
【注】在现代 CPU 中,f64
与 f32
速度几乎是一样的。
2.3 布尔类型
Rust 中的布尔类型用 bool
声明。和其他语言类似,它两种取值:true
和 false
。
2.4 字符类型
Rust 中的字符类型用 char
声明,它是 Rust 中最原生的字母类型。char
使用单引号指定,不同于字符串使用双引号指定。
- Rust 的
char
类型大小为四个字节,代表了一个 Unicode 标量值。 - 在 Rust 中,拼音字母、中文、日文、韩文等文字字符,甚至 emoji 和零长度的空白符都是有效的 char 值。
3. 复合类型
复合类型(compound)可以将多个值组合成一个类型,Rust 中原生的复合类型有:元组(tuple)、数组(array)、结构体(struct)。
3.1 元组
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定,一旦声明,其长度不能改变。元组的声明语法如下:
代码语言:javascript复制let tup: (i32, f64, u8) = (500, 6.4, 1);
tup
变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配来「解构」元组值,或者直接使用 .
运算符按索引值(索引值从 0 开始)访问:
// 解构
let (x, y, z) = tup;
// . 运算符
let x = tup.0;
let y = tup.1;
let z = tup.2;
3.2 数组
另一个包含多个值的方式是数组,与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组是固定长度的,一旦声明,其长度不能改变。数组的声明语法如下:
代码语言:javascript复制// 自动计算长度
let arr = [1, 2, 3, 4, 5];
// 指定类型和长度
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// 创建重复元素数组
let arr = [3; 5]; // 创建包含 5 个元素值均为 3 的数组
- 数组是一整块分配在栈上的内存,可以使用索引来访问数组的元素:
let first = arr[0];
let second = arr[1];
- 如果访问数组时索引溢出,在编译时可以通过,但在运行时会报错 panic 而退出。
【注】Rust 标准库中提供了 vector
集合类型,它可以实现数组长度的动态变化。
3.3 结构体
结构体和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
- 定义结构的基本语法类似如下:
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
- 定义了结构体之后,可以通过为每个字段指定具体值来创建这个结构体的实例。
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
- 为了从结构体中获取某个特定的值,可以使用
.
运算符。 - 若想改变结构体实例中某个字段的值,则要求整个实例必须是可变的。Rust 并不允许只将某个字段标记为可变。
元组结构体 可以定义与元组类似的结构体,称为「元组结构体」。
- 元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。定义元组结构体举例如下:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
- 不同元组结构体的实例是不相同的,不能相互替代,即使结构体中的字段有着相同的类型。
- 在其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用
.
后跟索引来访问单独的值。
自动引用和解引用
- 在 C/C 语言中,有两个不同的运算符来调用字段:
.
直接在对象上调用字段,而->
在一个对象的指针上调用字段,这时需要先解引用(dereference)指针。 - Rust 并没有一个与
->
等效的运算符;相反,Rust 有一个叫「自动引用和解引用」(automatic referencing and dereferencing)的功能。- 当
object
调用字段时,Rust 会自动为object
添加&
、&mut
或*
以便使object
与字段签名匹配。即object.field
和(&object).field
等价。
- 当
3.4 枚举
枚举(enumerations)允许通过列举可能的成员(variants)来定义一个类型。利用枚举列出可能的 IP 地址类型举例如下:
代码语言:javascript复制enum IpAddrKind {
V4,
V6,
}
- 访问枚举中的成员使用
::
语法:IpAddrKind::V4
和IpAddrKind::V6
。 - 每个枚举成员都是定义的枚举类型,即
IpAddrKind::V4
和IpAddrKind::V6
都是IpAddrKind
类型。 - 枚举还能将数据直接和枚举的每一个成员绑定,这样就不需要额外定义结构体来关联枚举成员和对应数据。
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
- 枚举的每一个成员可以处理不同类型和数量的数据。枚举成员甚至可以包含另一个枚举。
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
Rust 标准库中常用枚举
- IpAddr 枚举:用来存储和编码 IP 地址。
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
- Option 枚举:用来表示一个值要么有值要么没值。
enum Option<T> {
Some(T),
None,
}