Bỏ qua để đến nội dung

Lỗi Rust E0164: expected tuple struct or tuple variant, found struct variant

E0164 nghĩa là: bạn viết ngoặc tròn trong một mẫu (pattern), như Shape::Circle(r), sau một cái tên không nhận giá trị nào trong ngoặc tròn. Với Rust hiện nay, bạn sẽ gặp nó trong ba tình huống: một biến thể enum có trường được đặt tên (Circle { radius: f64 }) bị so khớp như thể nó là biến thể tuple, một hàm liên kết như Celsius::new(d) bị dùng làm mẫu, và một hằng số liên kết như Volume::MAX(_) có ngoặc tròn phía sau. Mỗi trường hợp đều có cách sửa ngắn và rõ ràng. (Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch 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 fields
8 + Shape::Circle { radius: _ } => 3.14 * r * r, // ❌ `Circle` has named fields
|

Thông báo của trình biên dịch luôn bằng tiếng Anh: nó nói rằng ở đây cần “tuple struct hoặc tuple variant”, nhưng lại gặp một “struct variant” (biến thể dạng struct). Đây là chương trình gây ra lỗi đó (chú thích được giữ bằng tiếng Anh vì nó xuất hiện nguyên văn trong thông báo lỗi):

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 }));
}

Dấu ^^^ phủ lên cả mẫu Shape::Circle(r), và nhãn cho biết Rust mong đợi gì ở đó: “a tuple struct or tuple variant” (một tuple struct hoặc biến thể tuple). Chỉ hai loại này được viết bằng ngoặc tròn trong một mẫu, vì chỉ chúng chứa giá trị theo vị trí, như Some(x) hoặc Meters(m).

Shape::Circle là một biến thể dạng struct (struct variant). Giá trị của nó có tên, radius, và trường có tên được so khớp bằng ngoặc nhọn: Shape::Circle { radius }.

Hãy cẩn thận với dòng help: ở đây. Nó gợi ý Shape::Circle { radius: _ }, sửa được mẫu nhưng lại bỏ qua bán kính. Nhánh của chúng ta dùng r, nên chép nguyên gợi ý sẽ gây ra một lỗi mới, E0425 cannot find value `r` in this scope (không tìm thấy giá trị r trong phạm vi này). Khi bạn cần giá trị, hãy đặt tên cho nó bên trong ngoặc nhọn, như ở cách sửa 1.

Giống như một tờ phiếu có các ô được ghi nhãn, “Rộng: … Cao: …”, và bạn lại viết “3, 4” bên cạnh tiêu đề. Các con số có thể đúng, nhưng tờ phiếu muốn mỗi số nằm trong đúng ô có nhãn của nó.

Một mẫu phải trông giống giá trị mà nó so khớp. Rust có hai cách để chứa dữ liệu, và mỗi cách có mẫu riêng (xem Enum có thể chứa dữ liệu):

Định nghĩa Cách tạo Cách so khớp
Circle(f64) (biến thể tuple) Shape::Circle(1.0) Shape::Circle(r)
Circle { radius: f64 } (biến thể dạng struct) Shape::Circle { radius: 1.0 } Shape::Circle { radius }

Trường có tên được so khớp theo tên, không theo vị trí, nên thứ tự trong mẫu không quan trọng, và thêm một trường về sau cũng không âm thầm đẩy lệch các trường khác. Trộn hai kiểu viết sẽ bị từ chối, nên không bao giờ có chuyện băn khoăn giá trị nào đi vào đâu.

Mẫu cũng được kiểm tra lúc biên dịch, nên chúng không thể chạy code. Đó là lý do một tên hàm theo sau là (d) cũng không thể là một mẫu, kể cả khi hàm đó tạo ra đúng giá trị bạn đang so khớp. Đọc thêm ở So khớp mẫu.

Cách sửa 1: Dùng ngoặc nhọn với tên trường

Phần tiêu đề “Cách sửa 1: Dùng ngoặc nhọn với tên trường”

Chọn cách này khi thông báo nói found struct variant, dạng E0164 phổ biến nhất. Viết tên trường (khi đó Rust tạo một biến cùng tên), field: new_name để chọn tên khác, hoặc { .. } để bỏ qua mọi trường:

enum Shape {
Circle { radius: f64 },
Rect { w: f64, h: f64 },
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle { radius } => 3.14 * radius * radius, // ✅ tên trường trong ngoặc nhọn
Shape::Rect { w: width, h: height } => width * height, // ✅ trường: tên mới
}
}
fn name(shape: &Shape) -> &'static str {
match shape {
Shape::Circle { .. } => "hình tròn", // ✅ “hình tròn nào cũng được”
Shape::Rect { .. } => "hình chữ nhật",
}
}
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));
}
}

Kết quả:

hình tròn: 3.14
hình chữ nhật: 6.00
▶ Tự chạy thử

Nếu bạn liệt kê một vài trường chứ không phải tất cả, hãy thêm .. ở cuối, như Shape::Rect { w, .. }. Bỏ nó đi sẽ gây ra E0027, còn gõ sai tên trường sẽ gây ra E0026.

Cách sửa 2: Đổi biến thể thành biến thể tuple

Phần tiêu đề “Cách sửa 2: Đổi biến thể thành biến thể tuple”

Chọn cách này khi ngoặc tròn thật sự là thứ bạn muốn: biến thể có một giá trị hiển nhiên, và bạn thích viết Shape::Circle(1.0) ở mọi nơi hơn. Hãy đổi định nghĩa, và mẫu Circle(r) trở nên đúng:

enum Shape {
Circle(f64), // ✅ giờ là biến thể tuple: một giá trị không tên
Rect { w: f64, h: f64 },
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r, // ✅ ngoặc tròn hợp với biến thể tuple
Shape::Rect { w, h } => w * h,
}
}
fn main() {
let small = Shape::Circle(1.0); // khi tạo cũng dùng ngoặc tròn
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));
}

Kết quả:

3.14 12.56 2.00
▶ Tự chạy thử

Việc này thay đổi kiểu, nên mọi chỗ tạo hoặc so khớp Circle cũng phải chuyển sang ngoặc tròn. Trình biên dịch sẽ liệt kê từng chỗ. Khi có từ hai giá trị trở lên, trường có tên thường rõ ràng hơn.

Cách sửa 3: So khớp chính tuple struct, và gọi hàm trong guard

Phần tiêu đề “Cách sửa 3: So khớp chính tuple struct, và gọi hàm trong guard”

Chọn cách này khi thông báo nói found associated function (gặp hàm liên kết) và nhãn ghi `fn` calls are not allowed in patterns (không được gọi fn trong mẫu). Bạn đã viết một hàm tạo như Celsius::new(d) ở chỗ Rust cần một mẫu:

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

Chương trình đằng sau lỗi này:

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 là một tuple struct, nên mẫu chính là tên của struct: Celsius(d). Bất cứ thứ gì cần gọi hàm hoặc phương thức đều đưa vào một match guard (if ... sau mẫu):

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 => "ấm", // ✅ chính tuple struct, cộng thêm một guard
Celsius(_) => "mát",
}
}
fn describe(t: &Celsius) -> &'static str {
match t {
t if t.is_freezing() => "đóng băng", // ✅ phương thức nằm trong guard
_ => "không đóng băng",
}
}
fn main() {
println!("{}", feels(Celsius::new(30.0)));
println!("{}", feels(Celsius::new(12.5)));
let Celsius(d) = Celsius::new(-3.0); // ✅ cũng dùng được với `let`
println!("{} độ", d);
println!("{}", describe(&Celsius::new(-3.0)));
}

Kết quả:

ấm
mát
-3 độ
đóng băng
▶ Tự chạy thử

Nếu trường của struct là riêng tư (struct nằm ở module khác), bạn không thể viết Celsius(d) bên ngoài module đó. Khi ấy hãy dùng một getter trong guard, như t if t.degrees() > 25.0. Xem Match guard: thêm một if.

Chọn cách này khi thông báo nói found associated constant (gặp hằng số liên kết). Một hằng số tự nó đã là một mẫu hoàn chỉnh. Nó khớp với đúng một giá trị, nên chẳng có gì để đặt trong ngoặc tròn:

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

Chương trình đằng sau lỗi này:

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));
}

Viết hằng số đứng một mình:

struct Volume;
impl Volume {
const MAX: u8 = 10;
}
fn label(level: u8) -> &'static str {
match level {
0 => "tắt tiếng",
Volume::MAX => "to nhất", // ✅ một hằng số, đứng một mình
1..=3 => "nhỏ",
_ => "bình thường",
}
}
fn main() {
for level in [0, 2, 7, 10, u8::MAX] {
println!("{}: {}", level, label(level));
}
}

Kết quả:

0: tắt tiếng
2: nhỏ
7: bình thường
10: to nhất
255: bình thường
▶ Tự chạy thử

Hằng số từ thư viện chuẩn cũng vậy: u32::MAX(_) là E0164, còn u32::MAX là một mẫu hợp lệ.

Sau khi đổi một biến thể sang trường có tên. Bạn đổi Move(i32, i32) thành Move { x: i32, y: i32 } cho rõ ràng. Giờ mọi nhánh Command::Move(x, y) => cũ đều báo E0164. Cách sửa: Command::Move { x, y } =>.

So khớp bằng một hàm “phân tích”. Kiểu như match input { Version::parse(v) => ... }, với hy vọng hàm sẽ chạy trong lúc so khớp. Cách sửa: gọi hàm trước, rồi so khớp trên kết quả: match Version::parse(input) { Some(v) => ..., None => ... }.

Hàm tự do, struct thông thường và biến thể unit thì cho ra E0532. Cùng sai sót ngoặc tròn đó được báo là E0532 khi cái tên không được truy cập qua một kiểu: một hàm thông thường (Some(limit()), expected a pattern, found a function call), một struct có trường được đặt tên (let Point(x, y) = p;, found struct `Point` ), hoặc một biến thể unit (Status::Active()). Cách sửa giống như trên trang này.

Kiểm tra nhanh

1. Với enum Shape { Circle { radius: f64 } }, nhánh nào dùng bán kính đúng cách?

2. Celsius::new(d) => ... báo “fn calls are not allowed in patterns”. Celsius là struct Celsius(f64);. Mẫu nào dùng được?

3. Bạn so khớp hằng số liên kết Volume::MAX thế nào?

Điểm: 0 / 3