跳转到内容

Rust 错误 E0532:expected unit struct, unit variant or constant, found tuple variant(模式形状不对)

E0532 的意思是:你写的模式的形状,和你点名的那个东西的形状对不上。 最典型的情况是,对一个带数据的变体 Circle(f64) 写了 Shape::Circle => 这样的 match 分支。Rust 要求加上括号:Shape::Circle(_)。反过来的错误也是这个错误码,只是换了一种说法“expected tuple struct or tuple variant”:在一个没有括号数据的东西后面加了括号,比如 Status::Active()。两种都只需改一行。(本页属于 Rust 编译错误详解系列。)

error[E0532]: expected unit struct, unit variant or constant, found tuple variant `Shape::Circle`
--> src/main.rs:8:9
|
2 | Circle(f64),
| ----------- `Shape::Circle` defined here
...
8 | Shape::Circle => "圆形", // ❌ `Circle` 带有一个值
| ^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `Shape::Circle(_)`

第一行的意思是“这里需要单元结构体、单元变体或常量,却找到了元组变体 Shape::Circle”。help 建议改用元组变体的模式写法 Shape::Circle(_)。

产生这个错误的程序是这样的:

enum Shape {
Circle(f64),
Square(f64),
}
fn name(shape: &Shape) -> &'static str {
match shape {
Shape::Circle => "圆形", // ❌ `Circle` 带有一个值
Shape::Square(_) => "正方形",
}
}
fn main() {
println!("{}", name(&Shape::Circle(1.0)));
}

^^^ 指着第 8 行的 Shape::Circle,第 2 行的 --- 则显示了它是怎么定义的:Circle(f64),一个携带一个 f64 的元组变体(tuple variant)。一个只有名字、后面什么都不跟的模式,必须是下面三种之一:

  • 单元结构体(unit struct),比如 struct Empty;
  • 单元变体(unit variant),即不带数据的变体,比如 Shape::Empty 或 None
  • 常量,比如 const LIMIT: u32 = 100;

Shape::Circle 哪种都不是。help 那一行直接给出了修法:Shape::Circle(_),意思是“一个圆,不管它的值是多少”。

E0532 的另一种标题正好反过来:expected tuple struct or tuple variant, found unit variant `Status::Active` (或者 found struct、found constant)。那种情况是你在名字后面加了括号,而你点名的东西并不接受括号里的值。

这就像一张表格上写着“宠物:狗(名字:____)”。只写“狗”,名字那一栏就空着;而表格上写的是“鱼”、根本没有名字栏,你却写了“鱼(名字:泡泡)”,就多出了一个不存在的栏。

每个结构体和枚举变体都是三种形状之一,而且创建它和匹配它时写法一样(见枚举可以携带数据):

种类 创建 匹配
单元:Empty Shape::Empty Shape::Empty
元组:Circle(f64) Shape::Circle(1.0) Shape::Circle(r) 或 Shape::Circle(..)
结构体:Rect { w: f64, h: f64 } Shape::Rect { w: 2.0, h: 3.0 } Shape::Rect { w, h } 或 Shape::Rect { .. }

因为形状要求很严格,模式看起来总是和它匹配的值一样,读 match 时就能看出每个分支有哪些数据。改类型时这也有帮助:如果 Empty 后来多了一个值、变成 Empty(u32),所有旧的 Shape::Empty 分支都会被标出来,而不会悄悄地去匹配新的东西。

修复 1:加上 (_)、(..) 或者给值起名字

Section titled “修复 1:加上 (_)、(..) 或者给值起名字”

当错误信息说 found tuple variant 或 found tuple struct 时选这个方法,这是最常见的 E0532。不需要的值,每个用一个 _;想一次跳过全部,用 ..;要用到这个值,就给它起个名字:

enum Shape {
Circle(f64),
Rect(f64, f64),
Empty,
}
fn name(shape: &Shape) -> &'static str {
match shape {
Shape::Circle(_) => "圆形", // ✅ 一个字段,忽略掉
Shape::Rect(..) => "矩形", // ✅ 不管几个字段,全部忽略
Shape::Empty => "空", // 单元变体:不加括号
}
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r, // ✅ 要用到这个字段,所以绑定它
Shape::Rect(w, h) => w * h,
Shape::Empty => 0.0,
}
}
fn main() {
let shapes = [Shape::Circle(1.0), Shape::Rect(2.0, 3.0), Shape::Empty];
for shape in &shapes {
println!("{}:{:.2}", name(shape), area(shape));
}
let circles = shapes.iter().filter(|s| matches!(s, Shape::Circle(_))).count();
println!("{} 个圆形", circles);
}

输出:

圆形:3.14
矩形:6.00
空:0.00
1 个圆形
▶ 动手试一试

对有好几个值的变体来说,(..) 很方便,以后再多加一个值它也照样能用。参见用 _ 和 .. 忽略一部分。

当错误信息说 found unit variant 或 found unit struct 时选这个方法。这个变体不带数据,括号里没什么可放的:

error[E0532]: expected tuple struct or tuple variant, found unit variant `Status::Active`
--> src/main.rs:8:9
|
2 | Active,
| ------ `Status::Active` defined here
...
8 | Status::Active() => "正常", // ❌ `Active` 不带数据
| ^^^^^^^^^^^^^^^^ help: use this syntax instead: `Status::Active`

背后的程序:

enum Status {
Active,
Banned(String),
}
fn label(s: &Status) -> &'static str {
match s {
Status::Active() => "正常", // ❌ `Active` 不带数据
Status::Banned(_) => "已封禁",
}
}
fn main() {
println!("{}", label(&Status::Active));
}

只写名字,在模式里和创建值的地方都一样:

#[derive(Debug)]
enum Status {
Active,
Banned(String),
}
fn label(s: &Status) -> String {
match s {
Status::Active => String::from("正常"), // ✅ 单元变体:不加 ()
Status::Banned(reason) => format!("已封禁({})", reason),
}
}
fn main() {
println!("{}", label(&Status::Active));
println!("{}", label(&Status::Banned(String::from("发垃圾广告"))));
let s = Status::Active; // ✅ 创建它的时候也不加 ()
println!("{:?}", s);
}

输出:

正常
已封禁(发垃圾广告)
Active
▶ 动手试一试

如果你本来就想让这个变体携带数据,那就改定义,比如用 Active(u64) 记录“从什么时候开始”的时间戳,这时 Status::Active(_) 才是正确的模式。

修复 3:带命名字段的结构体用花括号

Section titled “修复 3:带命名字段的结构体用花括号”

当错误信息说 found struct 时选这个方法。带命名字段的结构体 struct Point { x: i32, y: i32 } 要用花括号加字段名来解构,而不是用括号:

error[E0532]: expected tuple struct or tuple variant, found struct `Point`
--> src/main.rs:8:9
|
1 | / struct Point {
2 | | x: i32,
3 | | y: i32,
4 | | }
| |_- `Point` defined here
...
8 | let Point(x, y) = p; // ❌ `Point` 的字段是有名字的
| ^^^^^^^^^^^ help: use struct pattern syntax instead: `Point { x, y }`

背后的程序:

struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 3, y: 4 };
let Point(x, y) = p; // ❌ `Point` 的字段是有名字的
println!("{} {}", x, y);
}

rustc 建议的 Point { x, y } 完全正确。想换一个变量名时,用 字段: 名字 的写法:

struct Point {
x: i32,
y: i32,
}
struct Meters(f64); // 元组结构体:这里用括号才对
fn main() {
let p = Point { x: 3, y: 4 };
let Point { x, y } = p; // ✅ 花括号加字段名
println!("x = {}, y = {}", x, y);
let q = Point { x: -1, y: 7 };
let Point { x: left, y: up } = q; // ✅ 解构时顺便改名
println!("left = {}, up = {}", left, up);
let Meters(m) = Meters(2.5); // ✅ 元组结构体,用元组模式
println!("{} m", m);
}

输出:

x = 3, y = 4
left = -1, up = 7
2.5 m
▶ 动手试一试

对于带命名字段的枚举变体,比如对 Rect { w: f64, h: f64 } 写 Shape::Rect(w, h),同样的错误会报另一个错误码 E0164。修法也一样:用花括号。

修复 4:用常量或匹配守卫代替函数调用

Section titled “修复 4:用常量或匹配守卫代替函数调用”

当错误信息说 expected a pattern, found a function call(需要一个模式,却找到了函数调用)时选这个方法。模式必须在编译时就确定,而函数调用做不到:

error[E0532]: expected a pattern, found a function call
--> src/main.rs:7:14
|
7 | Some(limit()) => "已满", // ❌ 函数调用不能当作模式
| ^^^^^ not a tuple struct or tuple variant
|
= note: function calls are not allowed in patterns: <https://doc.rust-lang.org/book/ch19-00-patterns.html>

背后的程序:

fn limit() -> u32 {
100
}
fn status(users: Option<u32>) -> &'static str {
match users {
Some(limit()) => "已满", // ❌ 函数调用不能当作模式
Some(_) => "可加入",
None => "已关闭",
}
}
fn main() {
println!("{}", status(Some(100)));
}

如果这个值永远不变,就把它写成 const,常量可以用在模式里。如果它必须计算出来,就把值绑定到一个名字上,在匹配守卫(match guard)里比较:

const LIMIT: u32 = 100; // 常量可以用在模式里
fn limit_for(premium: bool) -> u32 {
if premium { 500 } else { 100 }
}
fn status(users: Option<u32>) -> &'static str {
match users {
Some(LIMIT) => "已满", // ✅ 一个常量
Some(_) => "可加入",
None => "已关闭",
}
}
fn status_for(users: Option<u32>, premium: bool) -> &'static str {
match users {
Some(n) if n >= limit_for(premium) => "已满", // ✅ 在守卫里调用函数
Some(_) => "可加入",
None => "已关闭",
}
}
fn main() {
println!("{}", status(Some(100)));
println!("{}", status(Some(42)));
println!("{}", status(None));
println!("{}", status_for(Some(100), false));
println!("{}", status_for(Some(100), true));
}

输出:

已满
可加入
已关闭
已满
可加入
▶ 动手试一试

匹配守卫的讲解见匹配守卫:再加一个 if。

单元变体多了一个值之后。 Event::Logout 以前是单元变体,你把它改成了 Logout(u64)。现在每个 Event::Logout => 分支都会报 E0532。修复:写成 Event::Logout(_) =>;如果需要这个值,就写 Event::Logout(at) =>。

只想检查值是哪一种。 如果你从 Java 或 TypeScript 转过来,可能会写 if let Shape::Circle = shape 来问“它是不是圆?”。应该写 if let Shape::Circle(_) = shape,或者用 matches!(shape, Shape::Circle(_)) 快速得到一个 bool。

不写 Shape:: 前缀时是 E0530。 如果你只写一个光秃秃的名字,比如 Ok =>,或者在 use Shape::*; 之后写 Circle =>,rustc 会把它当成一个新变量。它报的是 E0530:match bindings cannot shadow tuple variants(match 绑定不能遮蔽元组变体),并附上 help:try specify the pattern arguments: `Ok(..)` 。修法一样:加上括号。

小测验

1. 对于 enum Shape { Circle(f64), Empty },哪个 match 分支能编译通过?

2. rustc 说“expected tuple struct or tuple variant, found unit variant Status::Active”。怎么修?

3. struct Point { x: i32, y: i32 }:怎样用 let 解构它?

得分: 0 / 3