콘텐츠로 이동

Rust 에러 E0532: expected unit struct, unit variant or constant, found tuple variant

E0532는 패턴의 모양이 내가 쓴 이름의 실제 모양과 맞지 않는다는 뜻이에요. 대표적인 경우는 데이터를 담는 배리언트 Circle(f64)에 대해 match 갈래를 Shape::Circle =>라고 쓴 거예요. 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", // ❌ `Circle`은 값을 담고 있어요
| ^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `Shape::Circle(_)`

“expected unit struct, unit variant or constant, found tuple variant Shape::Circle“은 “유닛 구조체, 유닛 배리언트, 상수 중 하나가 와야 하는데 튜플 배리언트 Shape::Circle이 왔다”는 뜻이에요. help:는 “튜플 배리언트 패턴 문법을 대신 쓰라”며 Shape::Circle(_)를 제안해요.

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

enum Shape {
Circle(f64),
Square(f64),
}
fn name(shape: &Shape) -> &'static str {
match shape {
Shape::Circle => "circle", // ❌ `Circle`은 값을 담고 있어요
Shape::Square(_) => "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처럼 데이터가 없는 배리언트
  • 상수(constant): 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: (_), (..), 또는 값의 이름 붙이기

섹션 제목: “방법 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개
▶ 직접 실행해 보기

(..)는 값이 여러 개인 배리언트에 편하고, 나중에 값을 하나 더 추가해도 그대로 동작해요. _와 ..로 일부 무시하기를 참고하세요.

방법 2: 유닛 배리언트에서 () 빼기

섹션 제목: “방법 2: 유닛 배리언트에서 () 빼기”

메시지에 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", // ❌ `Active`에는 데이터가 없어요
| ^^^^^^^^^^^^^^^^ help: use this syntax instead: `Status::Active`

help는 “이 문법을 대신 쓰라”며 괄호 없는 Status::Active를 제안해요. 문제가 되는 프로그램은 이래요.

enum Status {
Active,
Banned(String),
}
fn label(s: &Status) -> &'static str {
match s {
Status::Active() => "active", // ❌ `Active`에는 데이터가 없어요
Status::Banned(_) => "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: 이름 있는 필드를 가진 구조체에는 중괄호 쓰기

섹션 제목: “방법 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 }`

help는 “구조체 패턴 문법을 대신 쓰라”고 해요. 문제가 되는 프로그램은 이래요.

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: 함수 호출 대신 상수나 매치 가드 쓰기

섹션 제목: “방법 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()) => "full", // ❌ 함수 호출은 패턴이 될 수 없어요
| ^^^^^ 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>

note:는 “패턴에서는 함수 호출을 쓸 수 없다”는 뜻이에요. 문제가 되는 프로그램은 이래요.

fn limit() -> u32 {
100
}
fn status(users: Option<u32>) -> &'static str {
match users {
Some(limit()) => "full", // ❌ 함수 호출은 패턴이 될 수 없어요
Some(_) => "open",
None => "closed",
}
}
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라고 쓰거나, 간단히 bool이 필요하면 matches!(shape, Shape::Circle(_))를 쓰세요.

Shape:: 없이 쓰면 E0530이 나요. Ok =>처럼, 또는 use Shape::*; 뒤에 Circle =>처럼 이름만 쓰면, rustc는 그것을 새 변수로 읽어요. 그래서 E0530, match bindings cannot shadow tuple variants(match 바인딩은 튜플 배리언트를 가릴 수 없다)를 내고, try specify the pattern arguments: `Ok(..)` 라는 help를 붙여요. 고치는 법은 같아요. 괄호를 붙이세요.

이해도 점검

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