复合类型:元组(Tuple)
本教程共 78 篇 · 第 11 篇 · 更新于 2026-08-08 · 约 6 分钟阅读
本节目标:学会用元组把不同类型的值打包成一个整体,掌握解构和用点号下标取值,并认识特殊的单元类型
()。
标量类型一次只装一个值。但现实里经常需要”把几个相关但不同类型的东西放一块”。比如一个点的坐标(整数 x、整数 y),或者一个人的简档(名字字符串、年龄整数)。Rust 提供两种复合类型干这事:元组和数组。这章讲元组。
11-1 什么是元组
元组(Tuple)是一组长度固定、类型可以不同的值,写在一对圆括号里,用逗号分隔:
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
println!("三个值:{} {} {}", tup.0, tup.1, tup.2);
}
(500, 6.4, 1) 是一个元组,类型是 (i32, f64, u8)——三个位置分别对应整数、浮点、无符号 8 位整数。类型标注可省,编译器能推断。
Note“长度固定”是元组的核心特征:一旦声明,里面有几个元素就定死了,不能后来加一个或减一个。这和后面要讲的 Vec(动态数组)正相反。
11-2 用点号下标访问
元组里的元素从 0 开始编号,访问时用 . 加序号:
let tup = (500, 6.4, 1);
let first = tup.0; // 500
let second = tup.1; // 6.4
let third = tup.2; // 1
这种写法叫”字段访问”。适合你只想拿其中某一两个值的时候。
11-3 解构:一次性拆开
更常用的玩法是”解构”(destructuring)——用一个模式把元组拆成几个独立变量:
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("x={x}, y={y}, z={z}");
}
let (x, y, z) = tup; 这行把元组的三个位置分别绑到 x、y、z 上。解构要求左边变量个数和元组长度一致,否则编译报错。
Tip解构是 Rust 的常用 idiom(惯用法)。函数返回多个值时,常把结果打包成元组返回,调用方用解构一次接住,比别的语言用引用参数传出优雅得多。
11-4 嵌套元组
元组里能再套元组,或套别的复合类型:
let nested = ((1, 2), (3, 4));
let ((a, b), (c, d)) = nested;
多层解构同样支持。刚开始写简单程序用不到嵌套,认识一下即可。
11-5 单元类型 ()
有个特殊的元组:空元组 (),叫单元类型。它表示一个”没有有意义返回值”的情况。
比如一个函数只打印、不返回东西,它的返回类型就是单元类型:
fn say_hi() {
println!("hi");
}
这里没写返回类型,Rust 默认返回 ()。你可以显式写成 fn say_hi() -> ()。() 就像别的语言里的 void,但 Rust 把它也当成一个正经的类型,统一了”一切都有类型”的设计。
Note单元类型占 0 字节内存,纯粹是个类型层面的占位。你很少手写它,但会在函数签名、或
println!的返回值(也是())里见着,知道它代表”空的、无值”就行。
11-6 元组适合什么场景
元组适合”临时把几个相关值捆一起”,尤其函数返回多值。但它有个短板:元素靠位置(第 0 个、第 1 个)区分,可读性差。如果一组值有名字语义(比如”用户名""年龄""是否会员”),更好的选择是下一章讲的数组(同类型集合)或后面章节的结构体(带名字的字段)。
Warning别用元组装”含义不清的一大堆数”。
(i32, i32, i32, i32, i32)这种五元组,过两周你自己都忘了第 3 个位置是宽度还是高度。字段多、有语义时,用结构体替代元组,代码会好读很多。
11-7 元组在函数里返回多值
元组和函数返回值是天生一对。如果一个函数要同时给出多个结果,与其定义一堆输出参数,不如把结果打包成元组返回,调用方用解构一次接住。比如写一个找最小最大值的函数:
fn min_max(a: i32, b: i32) -> (i32, i32) {
if a < b {
(a, b)
} else {
(b, a)
}
}
fn main() {
let (lo, hi) = min_max(10, 3);
println!("最小 {lo},最大 {hi}");
}
返回类型写成 (i32, i32),函数体末行用 (a, b) 返回,调用方用 let (lo, hi) = 解构。这比”传入两个 &mut 参数把结果写回去”清晰得多,也是 Rust 返回多值的惯用法。
Tip如果返回的值超过三个、且每个都有明确含义(比如”状态码、消息、时间戳”),元组就开始不好读——调用方得记住第几个位置是什么。这时换成结构体(给字段起名字)可读性更好。元组适合”两三个、含义自明”的临时组合。
11-8 解构在 for 循环里
解构在 for 循环里配合元组也很好用。比如你有一个”坐标对”的数组,遍历时能直接拆开:
let points = [(1, 2), (3, 4), (5, 6)];
for (x, y) in points {
println!("点 ({x}, {y})");
}
for (x, y) in points 把每个元组元素解构成 x 和 y。这种”遍历 + 同时拆出多个字段”的写法很常见,后面学哈希表(HashMap)遍历键值对时也会用到同样的解构。
Tip解构不限于元组:结构体、枚举变体、甚至数组都能用模式解构。它是 Rust “模式”能力的一部分,和后面
match共享同一套语法。先在这里建立”解构 = 把复合值拆成多个变量”的直觉。
11-9 元组作为返回值的代价
元组作为”轻量返回值”虽方便,但也有代价:它是位置语义,调用方得记住”第 0 个是什么、第 1 个是什么”。当返回两项含义相近(如宽和高),还勉强记得;一旦返回三项以上且含义不同(如状态码、消息、数据),tuple.0、tuple.1、tuple.2 读起来就费劲,还容易把位置搞反。
所以经验是:两项的临时组合用元组很香;三项及以上、或每项有独立业务含义,就换成结构体,用字段名取代位置。Rust 不拦你用大元组,但社区会默默建议你”该升级成结构体了”。
Tip判断标准很简单:如果你发现自己要给返回的元组写注释”// 0 是宽,1 是高”,那这就是结构体在向你招手。字段名是自解释的文档,位置编号不是。
11-10 解构时忽略不关心的元素
解构时如果只想要其中几个值,其余不需要,可以用下划线 _ 占位,表示”这里有值但我不要”:
fn main() {
let tup = (1, 2, 3);
let (first, _, third) = tup; // first=1, third=3,中间的 2 被忽略
println!("首 {first},尾 {third}");
}
_ 不用起名字、也不会报”未使用变量”,多个不想要的就写多个 _。如果你连中间一长串都不要,用 .. 一次性跳过更省事:
fn main() {
let tup = (1, 2, 3, 4, 5);
let (first, .., last) = tup; // first=1, last=5,中间全跳
println!("首 {first},尾 {last}");
}
.. 代表”中间不管多少元素都忽略”,但一个解构里只能用一次,而且编译器得能确定剩下几个位置对得上。这个技巧在遍历返回大元组、你只关心首尾时特别顺手。
Note
_和..都是”模式”的一部分。解构本质就是用模式把复合值拆开,所以这套语法和后面match、结构体解构完全打通。先在这里练手感,后面学模式匹配会轻松很多。
11-11 你可能会踩的坑
元组看着简单,初学时还是有几个高频坑,提前知道能少花时间:
坑一:下标写法搞混。元组访问用 tup.0、tup.1(点号加序号),不是 tup[0](方括号是数组的)。从别的语言转来常写 tup[0],编译器会提示”元组不支持下标索引”。记牢:圆括号配点号,数组才配方括号。
坑二:类型对不上。元组每个位置的类型是固定的,tup.1 是 f64 你就不能把它当 i32 直接传给要整数的函数,会报类型不匹配,得显式 as 转换。
坑三:解构左边变量个数必须和元组长度一模一样。写 let (a, b) = (1, 2, 3); 会报错,因为左边两个、右边三个。少一个多一个都不行,编译器会精确告诉你”模式里有 2 个绑定,但元组有 3 个元素”。
坑四:把元组当结构体使。一旦你开始给元组写注释”// 第 0 个是宽,第 1 个是高”,那就是结构体在向你招手——字段名比位置好记得多,也自解释。
11-12 小结
元组把不同类型的值打包、长度固定;用 .序号 访问,用 let (a,b) = 解构,用 _ 和 .. 忽略不关心的值;空元组 () 是单元类型,代表”无返回值”。下章讲数组——它和元组相反:长度固定、但里面必须同类型,适合装”一批同类的数”。