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 有两个原生的浮点数类型:f32f64,默认浮点数类型是 f64。浮点数采用 IEEE-754 标准表示,法2 是单精度浮点数,f64 是双精度浮点数。

【注】在现代 CPU 中,f64f32 速度几乎是一样的。

2.3 布尔类型

Rust 中的布尔类型用 bool 声明。和其他语言类似,它两种取值:truefalse

2.4 字符类型

Rust 中的字符类型用 char 声明,它是 Rust 中最原生的字母类型。char 使用单引号指定,不同于字符串使用双引号指定。

  • Rust 的 char 类型大小为四个字节,代表了一个 Unicode 标量值。
  • 在 Rust 中,拼音字母、中文、日文、韩文等文字字符,甚至 emoji 和零长度的空白符都是有效的 char 值。

3. 复合类型

复合类型(compound)可以将多个值组合成一个类型,Rust 中原生的复合类型有:元组(tuple)、数组(array)、结构体(struct)。

3.1 元组

元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定,一旦声明,其长度不能改变。元组的声明语法如下:

1
let tup: (i32, f64, u8) = (500, 6.4, 1);

tup 变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配来「解构」元组值,或者直接使用 . 运算符按索引值(索引值从 0 开始)访问:

1
2
3
4
5
6
// 解构
let (x, y, z) = tup;
// . 运算符
let x = tup.0;
let y = tup.1;
let z = tup.2;

3.2 数组

另一个包含多个值的方式是数组,与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组是固定长度的,一旦声明,其长度不能改变。数组的声明语法如下:

1
2
3
4
5
6
// 自动计算长度
let arr = [1, 2, 3, 4, 5];
// 指定类型和长度
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// 创建重复元素数组
let arr = [3; 5]; // 创建包含 5 个元素值均为 3 的数组
  • 数组是一整块分配在栈上的内存,可以使用索引来访问数组的元素:
1
2
let first = arr[0];
let second = arr[1];
  • 如果访问数组时索引溢出,在编译时可以通过,但在运行时会报错 panic 而退出。

【注】Rust 标准库中提供了 vector 集合类型,它可以实现数组长度的动态变化。

3.3 结构体

结构体和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。

  • 定义结构的基本语法类似如下:
1
2
3
4
5
6
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
  • 定义了结构体之后,可以通过为每个字段指定具体值来创建这个结构体的实例。
1
2
3
4
5
6
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
  • 为了从结构体中获取某个特定的值,可以使用 . 运算符。
  • 若想改变结构体实例中某个字段的值,则要求整个实例必须是可变的。Rust 并不允许只将某个字段标记为可变。

元组结构体

可以定义与元组类似的结构体,称为「元组结构体」。

  • 元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。定义元组结构体举例如下:
1
2
3
4
5
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 等价。
1
2
3
4
5
6
7
8
9
10
11
12
13
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };

println!(
"width: {}, height: {}",
rect.width,
(&rect).height
);
}

3.4 枚举

枚举(enumerations)允许通过列举可能的成员(variants)来定义一个类型。利用枚举列出可能的 IP 地址类型举例如下:

1
2
3
4
enum IpAddrKind {
V4,
V6,
}
  • 访问枚举中的成员使用 :: 语法:IpAddrKind::V4IpAddrKind::V6
  • 每个枚举成员都是定义的枚举类型,即 IpAddrKind::V4IpAddrKind::V6 都是 IpAddrKind 类型。
  • 枚举还能将数据直接和枚举的每一个成员绑定,这样就不需要额外定义结构体来关联枚举成员和对应数据。
1
2
3
4
5
6
7
enum IpAddr {
V4(String),
V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
  • 枚举的每一个成员可以处理不同类型和数量的数据。枚举成员甚至可以包含另一个枚举。
1
2
3
4
5
6
7
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 标准库中常用枚举

  1. IpAddr 枚举:用来存储和编码 IP 地址。
1
2
3
4
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
  1. Option 枚举:用来表示一个值要么有值要么没值。
1
2
3
4
enum Option<T> {
Some(T),
None,
}