Rust 错误 E0164:expected tuple struct or tuple variant, found struct variant(对结构体变体用了括号)
E0164 的意思是:你在模式里写了括号,比如 Shape::Circle(r),可括号前面的那个名字并不接受括号里的值。 在现在的 Rust 里,你会在三种情况下碰到它:把带命名字段的枚举变体(Circle { radius: f64 })当成元组变体来匹配;把 Celsius::new(d) 这样的关联函数当成模式;在 Volume::MAX(_) 这样的关联常量后面加了括号。每种情况都有一个简短明确的修法。(本页属于 Rust 编译错误详解系列。)
error[E0164]: expected tuple struct or tuple variant, found struct variant `Shape::Circle` --> src/main.rs:8:9 |8 | Shape::Circle(r) => 3.14 * r * r, // ❌ `Circle` has named fields | ^^^^^^^^^^^^^^^^ not a tuple struct or tuple variant |help: the struct variant's field is being ignored |8 - Shape::Circle(r) => 3.14 * r * r, // ❌ `Circle` has named fields8 + Shape::Circle { radius: _ } => 3.14 * r * r, // ❌ `Circle` has named fields |第一行的意思是“这里需要元组结构体或元组变体,找到的却是结构体变体 Shape::Circle”。
产生这个错误的程序是这样的:
enum Shape { Circle { radius: f64 }, Square { side: f64 },}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(r) => 3.14 * r * r, // ❌ `Circle` has named fields Shape::Square { side } => side * side, }}
fn main() { println!("{}", area(&Shape::Square { side: 2.0 }));}这个错误是什么意思
Section titled “这个错误是什么意思”^^^ 标出了整个模式 Shape::Circle(r),标注说明了 Rust 在这里期望的是什么:“a tuple struct or tuple variant”(元组结构体或元组变体)。模式里只有这两种东西用括号来写,因为只有它们是按位置存放值的,比如 Some(x) 或 Meters(m)。
Shape::Circle 是一个结构体变体(struct variant)。它的值有名字 radius,而命名字段要用花括号来匹配:Shape::Circle { radius }。
这里的 help: 要小心看。它建议写 Shape::Circle { radius: _ },这样模式是修好了,但半径被忽略了。我们这个分支要用到 r,所以照抄这条建议会得到一个新错误 E0425 cannot find value `r` in this scope(在当前作用域找不到值 r)。需要这个值的时候,就像修复 1 那样,在花括号里给它起个名字。
就像一张带标签格子的表格:“宽:…… 高:……”,而你在标题旁边写了“3, 4”。数字也许没错,但表格要求每个数字填进对应标签的格子里。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”模式必须长得像它要匹配的值。Rust 有两种存放数据的方式,每种都有自己的模式写法(参见枚举可以携带数据):
| 定义方式 | 创建方式 | 匹配方式 |
|---|---|---|
Circle(f64)(元组变体) |
Shape::Circle(1.0) |
Shape::Circle(r) |
Circle { radius: f64 }(结构体变体) |
Shape::Circle { radius: 1.0 } |
Shape::Circle { radius } |
命名字段是按名字匹配的,而不是按位置,所以模式里的顺序无关紧要,以后加一个字段也不会悄悄把其他字段挤错位置。Rust 不允许混用这两种写法,这样就永远不会搞不清哪个值对应哪里。
模式还会在编译时检查,所以模式里不能运行代码。这就是为什么函数名加上 (d) 也不能当模式用,哪怕这个函数构造出来的正好是你要匹配的那种值。详见模式匹配。
修复 1:用花括号写出字段名
Section titled “修复 1:用花括号写出字段名”当错误信息里写着 found struct variant 时选这个方法,这也是最常见的 E0164。写出字段名(Rust 会用这个名字创建一个变量),或者用 字段: 新名字 换个名字,或者用 { .. } 忽略所有字段:
enum Shape { Circle { radius: f64 }, Rect { w: f64, h: f64 },}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle { radius } => 3.14 * radius * radius, // ✅ 花括号里写字段名 Shape::Rect { w: width, h: height } => width * height, // ✅ 字段: 新名字 }}
fn name(shape: &Shape) -> &'static str { match shape { Shape::Circle { .. } => "circle", // ✅ “任意一个圆” Shape::Rect { .. } => "rectangle", }}
fn main() { let shapes = [Shape::Circle { radius: 1.0 }, Shape::Rect { w: 2.0, h: 3.0 }]; for shape in &shapes { println!("{}: {:.2}", name(shape), area(shape)); }}输出:
circle: 3.14rectangle: 6.00如果只列出一部分字段,就在最后加上 ..,比如 Shape::Rect { w, .. }。漏掉它会得到 E0027,字段名拼错则会得到 E0026。
修复 2:把变体改成元组变体
Section titled “修复 2:把变体改成元组变体”当你确实想用括号时选这个方法:这个变体只有一个一看就懂的值,你更愿意到处写 Shape::Circle(1.0)。改一下定义,Circle(r) 这个模式就对了:
enum Shape { Circle(f64), // ✅ 现在是元组变体:一个没有名字的值 Rect { w: f64, h: f64 },}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(r) => 3.14 * r * r, // ✅ 元组变体正好用括号 Shape::Rect { w, h } => w * h, }}
fn main() { let small = Shape::Circle(1.0); // 创建时也用括号 let big = Shape::Circle(2.0); let door = Shape::Rect { w: 1.0, h: 2.0 }; println!("{:.2} {:.2} {:.2}", area(&small), area(&big), area(&door));}输出:
3.14 12.56 2.00这会改变类型本身,所以所有创建或匹配 Circle 的地方都得改成括号写法。编译器会把它们一个个列出来。如果有两个或更多的值,命名字段通常更清楚。
修复 3:匹配元组结构体本身,把函数调用放进守卫
Section titled “修复 3:匹配元组结构体本身,把函数调用放进守卫”当错误信息里写着 found associated function、标注写着 `fn` calls are not allowed in patterns(模式里不允许调用 fn)时选这个方法。你在 Rust 需要模式的地方写了 Celsius::new(d) 这样的构造函数:
error[E0164]: expected tuple struct or tuple variant, found associated function `Celsius::new` --> src/main.rs:11:9 |11 | Celsius::new(d) if d > 25.0 => "warm", // ❌ `new` is a function, not a pattern | ^^^^^^^^^^^^^^^ `fn` calls are not allowed in patterns背后的程序:
struct Celsius(f64);
impl Celsius { fn new(degrees: f64) -> Celsius { Celsius(degrees) }}
fn feels(t: Celsius) -> &'static str { match t { Celsius::new(d) if d > 25.0 => "warm", // ❌ `new` is a function, not a pattern _ => "cool", }}
fn main() { println!("{}", feels(Celsius::new(30.0)));}Celsius 是元组结构体,所以模式就是结构体自己的名字:Celsius(d)。凡是需要调用函数或方法的判断,都放进匹配守卫(模式后面的 if ...):
struct Celsius(f64);
impl Celsius { fn new(degrees: f64) -> Celsius { Celsius(degrees) }
fn is_freezing(&self) -> bool { self.0 <= 0.0 }}
fn feels(t: Celsius) -> &'static str { match t { Celsius(d) if d > 25.0 => "warm", // ✅ 元组结构体本身,再加一个守卫 Celsius(_) => "cool", }}
fn describe(t: &Celsius) -> &'static str { match t { t if t.is_freezing() => "freezing", // ✅ 方法调用放进守卫 _ => "not freezing", }}
fn main() { println!("{}", feels(Celsius::new(30.0))); println!("{}", feels(Celsius::new(12.5))); let Celsius(d) = Celsius::new(-3.0); // ✅ 用在 `let` 里也可以 println!("{} degrees", d); println!("{}", describe(&Celsius::new(-3.0)));}输出:
warmcool-3 degreesfreezing如果这个结构体的字段是私有的(它定义在另一个模块里),你在那个模块之外就不能写 Celsius(d)。这时在守卫里用 getter 方法,比如 t if t.degrees() > 25.0。参见匹配守卫:再加一个 if。
修复 4:去掉常量后面的括号
Section titled “修复 4:去掉常量后面的括号”当错误信息里写着 found associated constant 时选这个方法。常量本身就是一个完整的模式,它只匹配一个确定的值,所以括号里没有东西可放:
error[E0164]: expected tuple struct or tuple variant, found associated constant `Volume::MAX` --> src/main.rs:10:9 |10 | Volume::MAX(_) => "loudest", // ❌ a constant takes no () | ^^^^^^^^^^^^^^ not a tuple struct or tuple variant背后的程序:
struct Volume;
impl Volume { const MAX: u8 = 10;}
fn label(level: u8) -> &'static str { match level { 0 => "muted", Volume::MAX(_) => "loudest", // ❌ a constant takes no () _ => "normal", }}
fn main() { println!("{}", label(10));}单独写出常量就行:
struct Volume;
impl Volume { const MAX: u8 = 10;}
fn label(level: u8) -> &'static str { match level { 0 => "muted", Volume::MAX => "loudest", // ✅ 常量单独写 1..=3 => "quiet", _ => "normal", }}
fn main() { for level in [0, 2, 7, 10, u8::MAX] { println!("{}: {}", level, label(level)); }}输出:
0: muted2: quiet7: normal10: loudest255: normal标准库里的常量也一样:u32::MAX(_) 会报 E0164,u32::MAX 才是合法的模式。
把变体改成命名字段之后。 为了更清楚,你把 Move(i32, i32) 改成了 Move { x: i32, y: i32 }。之前所有的 Command::Move(x, y) => 分支现在都会报 E0164。修复:写成 Command::Move { x, y } =>。
想用“解析”函数来匹配。 比如写 match input { Version::parse(v) => ... },希望匹配时顺便运行这个函数。修复:先调用函数,再对结果做匹配:match Version::parse(input) { Some(v) => ..., None => ... }。
普通函数、普通结构体和单元变体报的是 E0532。 同样是括号用错,如果这个名字不是通过某个类型访问的,报的就是 E0532:普通函数(Some(limit()),expected a pattern, found a function call)、带命名字段的结构体(let Point(x, y) = p;,found struct `Point` ),或者单元变体(Status::Active())。修法和本页一样。
- E0532:expected tuple struct or tuple variant, found unit variant or struct:同样的混淆,发生在普通结构体、单元变体和普通函数上;也包括反过来的情况(元组变体没写括号)。
- E0533:expected unit struct, unit variant or constant, found struct variant:结构体变体完全没写花括号,比如
Shape::Circle =>。 - E0026:struct or variant does not have a field named x:花括号用对了,但里面的字段名写错了。
- E0027:pattern does not mention field x:花括号里漏了字段;加上
..。 - E0425:cannot find value in this scope:照抄
{ radius: _ }的建议却还在用r时会得到的错误。 - 所有编译错误详解
小测验
1. 对于 enum Shape { Circle { radius: f64 } },哪个分支正确地用到了半径?
2. Celsius::new(d) => ... 报了“fn calls are not allowed in patterns”。Celsius 的定义是 struct Celsius(f64);。哪个模式能用?
3. 怎样匹配关联常量 Volume::MAX?
得分: 0 / 3
- E0164 — Rust error codes index(英文):官方解释,附有关联常量的示例。
- 模式语法 — Rust 程序设计语言:用花括号或括号解构枚举、用
..忽略字段,以及匹配守卫。 - 解构枚举 — 通过例子学 Rust:元组变体和结构体变体放在一起匹配。
- 模式 — The Rust Reference(英文):哪些路径可以出现在元组结构体模式和结构体模式里。