跳转到内容

Rust 元组

元组(tuple)可以把几个值组合成一个整体,这些值的类型可以各不相同。想把几样相关的东西快速打包在一起、又不想专门定义一个新类型时,元组就很方便。

fn main() {
let person: (&str, i32, bool) = ("小明", 30, true);
println!("{:?}", person);
}

输出:

("小明", 30, true)
▶ 动手试一试

注意这个元组在一个值里混合了 &str、i32 和 bool。数组做不到这一点,因为数组里的每个元素都必须是同一种类型。

要打印整个元组,用 {:?}(调试格式),而不是 {}。普通的 {} 不能用来打印元组。

用 .0、.1、.2 等等来访问。元组的索引从 0 开始:

fn main() {
let person = ("小明", 30, true);
println!("姓名:{}", person.0);
println!("年龄:{}", person.1);
println!("活跃:{}", person.2);
}

输出:

姓名:小明
年龄:30
活跃:true
▶ 动手试一试

位置必须是直接写在代码里的数字,不能用变量,比如 person.i。因为每个位置上的类型可能都不一样,而 Rust 必须在编译时就知道类型。

一行代码就能把每个值分别取到各自的变量里:

fn main() {
let person = ("小明", 30, true);
let (name, age, is_active) = person;
println!("{name} 今年 {age} 岁,活跃:{is_active}");
}

输出:

小明 今年 30 岁,活跃:true
▶ 动手试一试

这通常比反复写 .0、.1、.2 更清楚,尤其是当元组有三个以上字段的时候。

函数只能声明一个返回类型,所以要返回不止一个值时,元组是最自然的选择:

fn min_max(numbers: &[i32]) -> (i32, i32) {
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
(min, max)
}
fn main() {
let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]);
println!("最小值:{smallest},最大值:{largest}");
}

输出:

最小值:1,最大值:9
▶ 动手试一试

先别管 &[i32]、.iter() 和 .unwrap(),它们会在 slice、迭代器和 Option 这几页讲到。这里要注意的是返回类型 (i32, i32),以及 main 怎样马上把它解构成两个变量。

如果元组是用 mut 声明的,就可以用同样的 .0、.1 位置逐个修改它的字段。值可以变,但形状不能变:元组的字段数量和类型永远和一开始一样。

解构也可以反过来用:把值赋给已有的变量。这样一行代码就能交换两个值,不需要临时变量。如果只想要其中一部分字段,不需要的就写 _:

fn main() {
let mut point = (3, 4);
point.0 += 10; // 原地修改一个字段
println!("point 是 {:?}", point);
let mut a = 1;
let mut b = 2;
(a, b) = (b, a); // 交换两个值
println!("a = {a}, b = {b}");
let (x, _) = point; // 保留 x,忽略第二个字段
println!("x = {x}");
}

输出:

point 是 (13, 4)
a = 2, b = 1
x = 13
▶ 动手试一试

交换之所以可行,是因为 Rust 会先构造出右边的 (b, a),然后才把它拆开赋给 a 和 b。_ 和模式还有很多用法,可以去模式匹配那一页看看。

其实你早就见过一个元组了,只是没意识到。() 叫作单元类型(unit type),当函数不返回任何有意义的值时,返回的就是它。它也是 fn main() 隐含的返回类型。

什么时候用元组,什么时候用结构体

Section titled “什么时候用元组,什么时候用结构体”

元组很适合快速、不起名字的组合,尤其是从函数返回两三个值的时候。一旦一组值代表一个真正的概念,需要带名字的字段(比如一个 Person,你会在代码各处按名字使用它的姓名和年龄),用结构体通常更好、更易读。

写一个函数 sum_and_product(a: i32, b: i32) -> (i32, i32),同时返回两个数的和与积。在 main 里用 5 和 6 调用它,并解构结果。提示:函数的最后一行就是元组本身 (a + b, a * b),不带分号。

查看答案
fn sum_and_product(a: i32, b: i32) -> (i32, i32) {
(a + b, a * b)
}
fn main() {
let (sum, product) = sum_and_product(5, 6);
println!("和 = {sum},积 = {product}");
}

输出:

和 = 11,积 = 30
▶ 动手试一试

矩形可以存成一个由两个点组成的元组,而每个点本身也是一个元组:let rect = ((1, 2), (5, 5));。算出它的宽、高和面积。提示:元组可以嵌套,rect.1.0 的意思是“第二个点的第一个字段”。

查看答案
fn main() {
let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0;
let height = rect.1.1 - rect.0.1;
println!("宽 {width},高 {height},面积 {}", width * height);
}

输出:

宽 4,高 3,面积 12
▶ 动手试一试

也可以一次解构两个点:let ((x1, y1), (x2, y2)) = rect;。这样要多打几个字,但 x2 - x1 比 rect.1.0 - rect.0.0 好读得多。当点号像这样越堆越多时,就说明用带命名字段的结构体会更清楚。

  • 元组把类型可能不同的值组合在一起:(name, age, is_active)。
  • 用 .0、.1、.2 访问字段,或者用解构一次全部取出来。
  • 元组是从函数返回多个值的自然方式。
  • mut 元组的字段可以修改,但它的大小和类型不能变。(a, b) = (b, a); 可以交换两个值。
  • 需要有名字、可复用的组合时,改用结构体。

小测验

1. 元组能存放、而数组不能存放的是什么?

2. 怎样访问名为 person 的元组的第二个元素?

3. 执行 let mut a = 1; let mut b = 2; (a, b) = (b, a); 之后,a 和 b 分别是多少?

得分: 0 / 3