콘텐츠로 이동

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`에는 이름 있는 필드가 있어요
| ^^^^^^^^^^^^^^^^ 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`에는 이름 있는 필드가 있어요
8 + Shape::Circle { radius: _ } => 3.14 * r * r, // ❌ `Circle`에는 이름 있는 필드가 있어요
|

“expected tuple struct or tuple variant, found struct variant Shape::Circle“은 “튜플 구조체나 튜플 배리언트가 와야 하는데 구조체 배리언트 Shape::Circle이 왔다”는 뜻이에요. 라벨 “not a tuple struct or tuple variant”는 “튜플 구조체도 튜플 배리언트도 아니다”라는 말이에요.

이 에러를 내는 프로그램은 다음과 같아요.

enum Shape {
Circle { radius: f64 },
Square { side: f64 },
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r, // ❌ `Circle`에는 이름 있는 필드가 있어요
Shape::Square { side } => side * side,
}
}
fn main() {
println!("{}", area(&Shape::Square { side: 2.0 }));
}

^^^는 패턴 Shape::Circle(r) 전체를 덮고 있고, 라벨은 그 자리에 Rust가 무엇을 기대했는지 알려 줘요. “튜플 구조체나 튜플 배리언트”요. 패턴에서 괄호로 쓰는 건 이 두 가지뿐이에요. Some(x)나 Meters(m)처럼 값을 위치로 담는 건 이 둘뿐이거든요.

Shape::Circle은 구조체 배리언트(struct variant)예요. 값에 radius라는 이름이 있고, 이름 있는 필드는 중괄호로 매칭해요. Shape::Circle { radius }처럼요.

여기서는 help:를 조심하세요. help는 Shape::Circle { radius: _ }를 제안하는데, 이렇게 하면 패턴은 고쳐지지만 반지름을 무시해요. 이 갈래는 r을 쓰고 있어서, 제안을 그대로 복사하면 새 에러 E0425 cannot find value `r` in this scope(이 스코프에서 값 r을 찾을 수 없다)가 나요. 값이 필요하다면 방법 1처럼 중괄호 안에서 이름을 붙이세요.

“가로: … 세로: …“처럼 칸마다 이름이 붙은 신청서에, 제목 옆에 “3, 4”라고 적은 것과 비슷해요. 숫자는 맞을지 몰라도, 신청서는 각 숫자를 이름 붙은 칸에 넣기를 원해요.

패턴은 매칭하는 값처럼 생겨야 해요. Rust에는 데이터를 담는 방법이 두 가지 있고, 각각 자기 패턴이 있어요(열거형은 데이터를 담을 수 있어요 참고).

정의 만들기 매칭하기
Circle(f64)(튜플 배리언트) Shape::Circle(1.0) Shape::Circle(r)
Circle { radius: f64 }(구조체 배리언트) Shape::Circle { radius: 1.0 } Shape::Circle { radius }

이름 있는 필드는 위치가 아니라 이름으로 매칭해요. 그래서 패턴 안의 순서는 상관없고, 나중에 필드를 추가해도 다른 필드가 몰래 밀려나지 않아요. 두 방식을 섞는 건 허용되지 않아서, 어느 값이 어디로 가는지 헷갈릴 일이 없어요.

또 패턴은 컴파일할 때 검사되기 때문에 코드를 실행할 수 없어요. 그래서 함수 이름 뒤에 (d)를 붙인 것도 패턴이 될 수 없어요. 그 함수가 매칭하려는 값을 정확히 만들어 내더라도요. 자세한 내용은 패턴 매칭을 참고하세요.

방법 1: 필드 이름과 함께 중괄호 쓰기

섹션 제목: “방법 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 { .. } => "원", // ✅ "어떤 원이든"
Shape::Rect { .. } => "직사각형",
}
}
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));
}
}

실행 결과:

원: 3.14
직사각형: 6.00
▶ 직접 실행해 보기

필드를 전부가 아니라 일부만 쓴다면 Shape::Rect { w, .. }처럼 끝에 ..를 붙이세요. 빼먹으면 E0027이 나고, 필드 이름에 오타가 있으면 E0026이 나요.

방법 2: 배리언트를 튜플 배리언트로 바꾸기

섹션 제목: “방법 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: 튜플 구조체 자체로 매칭하고, 함수는 가드에서 호출하기

섹션 제목: “방법 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`는 함수예요. 패턴이 아니에요
| ^^^^^^^^^^^^^^^ `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`는 함수예요. 패턴이 아니에요
_ => "cool",
}
}
fn main() {
println!("{}", feels(Celsius::new(30.0)));
}

Celsius는 튜플 구조체라서, 패턴은 구조체 자신의 이름인 Celsius(d)예요. 함수나 메서드 호출이 필요한 것은 모두 매치 가드(match guard), 즉 패턴 뒤의 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 => "따뜻해요", // ✅ 튜플 구조체 자체 + 가드
Celsius(_) => "선선해요",
}
}
fn describe(t: &Celsius) -> &'static str {
match t {
t if t.is_freezing() => "얼음이 얼어요", // ✅ 메서드는 가드에서
_ => "얼지 않아요",
}
}
fn main() {
println!("{}", feels(Celsius::new(30.0)));
println!("{}", feels(Celsius::new(12.5)));
let Celsius(d) = Celsius::new(-3.0); // ✅ `let`에서도 돼요
println!("{}도", d);
println!("{}", describe(&Celsius::new(-3.0)));
}

실행 결과:

따뜻해요
선선해요
-3도
얼음이 얼어요
▶ 직접 실행해 보기

구조체의 필드가 비공개라면(다른 모듈에 있다면) 그 모듈 밖에서는 Celsius(d)라고 쓸 수 없어요. 그럴 때는 t if t.degrees() > 25.0처럼 가드에서 getter를 쓰세요. 매치 가드: if 덧붙이기를 참고하세요.

메시지에 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", // ❌ 상수에는 ()를 붙이지 않아요
| ^^^^^^^^^^^^^^ 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", // ❌ 상수에는 ()를 붙이지 않아요
_ => "normal",
}
}
fn main() {
println!("{}", label(10));
}

상수만 쓰세요.

struct Volume;
impl Volume {
const MAX: u8 = 10;
}
fn label(level: u8) -> &'static str {
match level {
0 => "음소거",
Volume::MAX => "최대", // ✅ 상수만 단독으로
1..=3 => "작게",
_ => "보통",
}
}
fn main() {
for level in [0, 2, 7, 10, u8::MAX] {
println!("{}: {}", level, label(level));
}
}

실행 결과:

0: 음소거
2: 작게
7: 보통
10: 최대
255: 보통
▶ 직접 실행해 보기

표준 라이브러리의 상수도 마찬가지예요. 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())가 그래요. 고치는 법은 이 페이지와 같아요.

이해도 점검

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