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가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”패턴은 매칭하는 값처럼 생겨야 해요. 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 덧붙이기를 참고하세요.
방법 4: 상수 뒤의 괄호 빼기
섹션 제목: “방법 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", // ❌ 상수에는 ()를 붙이지 않아요 | ^^^^^^^^^^^^^^ 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())가 그래요. 고치는 법은 이 페이지와 같아요.
관련 에러
섹션 제목: “관련 에러”- 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 (영어): 연관 상수 예제가 있는 공식 설명이에요.
- 패턴 문법 — The Rust Programming Language 한국어판: 중괄호나 괄호로 열거형 해체하기,
..로 필드 무시하기, 매치 가드를 다뤄요. - Destructuring enums — Rust by Example (영어): 튜플 배리언트와 구조체 배리언트를 나란히 매칭해요.
- Patterns — The Rust Reference (영어): 튜플 구조체 패턴과 구조체 패턴에 어떤 경로가 올 수 있는지 정한 규칙이에요.