콘텐츠로 이동

Rust 에러 E0609: no field X on type T (없는 필드에 접근)

E0609는 value.something이라고 썼는데, value의 타입에 something이라는 필드가 없다는 뜻이에요. 대부분은 오타이거나, 값이 생각한 타입과 조금 다른 경우예요(예를 들어 User가 아니라 Option<User>일 때). 에러 메시지가 Rust가 보고 있는 타입을 정확히 알려 주니까, 보통은 그것만 보고도 고칠 수 있어요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.

error[E0609]: no field `emial` on type `User`
--> src/main.rs:12:25
|
12 | println!("{}", user.emial); // ❌ 오타: 필드 이름은 `email`이에요
| ^^^^^ unknown field
|
help: a field with a similar name exists
|
12 - println!("{}", user.emial); // ❌ 오타: 필드 이름은 `email`이에요
12 + println!("{}", user.email); // ❌ 오타: 필드 이름은 `email`이에요
|

“no field emial on type User“는 “User 타입에 emial이라는 필드가 없다”는 뜻이에요. 라벨 unknown field는 “알 수 없는 필드”, help는 “비슷한 이름의 필드가 있다”는 뜻이고, 바꿀 줄을 그대로 보여 줘요.

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

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.emial); // ❌ 오타: 필드 이름은 `email`이에요
}

첫 줄은 두 부분으로 되어 있고, 둘 다 중요해요.

  • no field `emial`: Rust가 찾지 못한, 점 뒤의 이름이에요.
  • on type `User`: Rust가 생각하는 user의 타입이에요.

그러니 먼저 이름을 확인하고, 그다음 타입을 확인하세요. 메시지 속 타입이 기대한 구조체가 아니라면, 예를 들어 Option<User>, &Option<User>, Vec<User>, &T 같은 타입이라면, 이름은 맞고 먼저 구조체를 꺼내야 하는 게 문제예요.

엉뚱한 가게에 서서 “두 번째 선반에 있는 파란 거 주세요”라고 하는 것과 비슷해요. 요청은 맞는데, 있는 곳이 틀린 거예요.

Rust에서 구조체의 필드는 구조체를 정의할 때 정해져요. 컴파일러는 전체 필드 목록을 알고, 모든 .필드를 컴파일할 때 검사해요. JavaScript처럼 “실행할 때 찾아보고 undefined를 받는” 일도, Python의 AttributeError도 없어요. 틀린 필드 이름은 절대 프로그램 안에 들어가지 못해요. 구조체를 참고하세요.

Option<T>와 Result<T, E> 같은 감싸는 타입은 별개의 타입이에요. 내 구조체가 그 안에 들어 있을 뿐, 감싸는 타입이 구조체의 필드를 갖고 있지는 않아요. Rust는 대신 안으로 손을 뻗어 주지 않아요. 값이 아예 없을 수도 있으니까요. 값이 없을 때 무슨 일이 일어나야 하는지 내가 정해야 해요. Option을 참고하세요.

제네릭 코드도 같은 엄격한 규칙을 따라요. fn greet<T>(x: &T)에서 T는 필드가 없는 i32나 bool까지 포함해 어떤 타입이든 될 수 있어요. Rust는 제네릭 함수를 가능한 모든 T에 대해 한 번에 검사하기 때문에, 트레이트 바운드가 약속한 것만 쓰게 해 줘요. 그리고 트레이트가 약속할 수 있는 건 메서드뿐이고, 필드는 약속할 수 없어요. 제네릭과 트레이트를 참고하세요.

메시지 속 타입이 기대한 구조체가 맞을 때 이 방법을 고르세요. help: a field with a similar name exists 줄을 찾아보세요. rustc가 어떤 필드를 말하려던 건지 거의 항상 알고 있어요.

struct User {
name: String,
email: String,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"),
};
println!("{}", user.name);
println!("{}", user.email); // ✅ 진짜 필드 이름이에요
}

실행 결과:

▶ 직접 실행해 보기

힌트가 없다면 struct 정의로 건너가서(대부분의 에디터에서 타입을 Ctrl+클릭하거나 F12) 이름을 거기서 복사하세요. 필드 이름은 대소문자를 구별해요. userName과 user_name은 서로 다른 필드예요.

방법 2: Option에서 구조체를 먼저 꺼내기

섹션 제목: “방법 2: Option에서 구조체를 먼저 꺼내기”

메시지가 타입을 Option<User>, Option<&User>, &Option<User>라고 할 때 이 방법을 고르세요. 무언가를 못 찾을 수도 있는 함수나 .first(), .last(), .get(), .find() 같은 메서드를 쓸 때 늘 일어나는 일이에요.

error[E0609]: no field `name` on type `Option<User>`
--> src/main.rs:11:25
|
11 | println!("{}", user.name);
| ^^^^ unknown field
|
help: one of the expressions' fields has a field of the same name
|
11 | println!("{}", user.unwrap().name);
| +++++++++

help는 “식의 필드 중 하나에 같은 이름의 필드가 있다”며 .unwrap()을 제안해요. 이 힌트대로 하면 컴파일은 되지만, 값이 None이면 프로그램이 죽어요. 더 좋은 방법은 두 경우에 각각 무슨 일이 일어나야 할지 정하는 거예요. if let, match, 또는 map과 기본값을 쓰세요.

struct User {
name: String,
}
fn find_user(id: u32) -> Option<User> {
if id == 1 {
Some(User { name: String::from("Ferris") })
} else {
None
}
}
fn main() {
// if let: User가 있을 때만 코드를 실행해요
if let Some(user) = find_user(1) {
println!("찾았어요: {}", user.name);
}
// match: 두 경우를 모두 처리해요
match find_user(2) {
Some(user) => println!("찾았어요: {}", user.name),
None => println!("id가 2인 사용자가 없어요"),
}
// map + unwrap_or: 필드를 꺼내거나, 기본값을 써요
let users = vec![User { name: String::from("Ada") }];
let first_name = users.first().map(|u| u.name.as_str()).unwrap_or("없음");
println!("첫 번째 사용자: {first_name}");
}

실행 결과:

찾았어요: Ferris
id가 2인 사용자가 없어요
첫 번째 사용자: Ada
▶ 직접 실행해 보기

Option의 참조자에도 같은 패턴이 통해요. maybe_user가 &Option<User>라면 if let Some(user) = maybe_user라고 쓰면 되고, 이때 user는 &User예요. (&Option<User>일 때는 rustc가 힌트를 전혀 주지 않으니, 기억해 둘 만해요.) Option을 반환하는 함수 안이라면 let user = find_user(1)?;도 쓸 수 있어요. match로 Option 다루기와 if let을 참고하세요.

방법 3: 튜플 구조체에는 .0과 .1 쓰기

섹션 제목: “방법 3: 튜플 구조체에는 .0과 .1 쓰기”

타입이 struct Point(i32, i32); 같은 튜플 구조체일 때 이 방법을 고르세요. 튜플 구조체의 필드에는 이름이 없고 위치만 있어서, p.x라고 쓰면 no field `x` on type `Point`가 나와요(힌트로 p.0을 알려 줘요). 위치를 쓰거나, 이름 있는 변수로 풀어내세요. 이름을 붙이고 싶다면 이름 있는 필드를 가진 구조체로 만드세요.

struct Point(i32, i32); // 튜플 구조체: 필드는 .0과 .1이에요
struct Size {
width: u32, // 이름 있는 필드: .width와 .height로 써요
height: u32,
}
fn main() {
let p = Point(3, 4);
println!("x = {}, y = {}", p.0, p.1);
let Point(x, y) = p; // 이름 있는 변수로 풀어낼 수도 있어요
println!("거리의 제곱 = {}", x * x + y * y);
let screen = Size { width: 1920, height: 1080 };
println!("{}x{}", screen.width, screen.height);
}

실행 결과:

x = 3, y = 4
거리의 제곱 = 25
1920x1080
▶ 직접 실행해 보기

일반 튜플도 똑같아요. 요소가 두 개인 튜플에 pair.2라고 쓰면 E0609가 나고, available fields are: `0`, `1`(쓸 수 있는 필드는 0, 1)이라는 note가 붙어요. 위치로 요소에 접근하기를 참고하세요.

방법 4: 제네릭 T에는 필드 대신 트레이트 메서드 쓰기

섹션 제목: “방법 4: 제네릭 T에는 필드 대신 트레이트 메서드 쓰기”

메시지가 on type `&T`(또는 T)라고 하고, type parameter 'T' declared here(타입 매개변수 T가 여기서 선언됨)라는 라벨이 붙어 있을 때 이 방법을 고르세요. 이런 코드는 넘기는 타입마다 전부 name 필드가 있더라도 실패해요.

fn greet<T>(animal: &T) {
println!("Hello, {}!", animal.name); // ❌ no field `name` on type `&T`
}

트레이트는 필드를 요구할 수 없지만, 게터 메서드(getter)는 요구할 수 있어요. 작은 트레이트를 정의하고, 타입마다 구현한 다음, T의 바운드로 추가하세요.

trait Named {
fn name(&self) -> &str;
}
struct Dog {
name: String,
}
struct Robot {
model: String,
}
impl Named for Dog {
fn name(&self) -> &str {
&self.name
}
}
impl Named for Robot {
fn name(&self) -> &str {
&self.model // 필드 이름이 같을 필요도 없어요
}
}
fn greet<T: Named>(thing: &T) {
println!("안녕하세요, {}!", thing.name()); // 필드가 아니라 메서드 호출이에요
}
fn main() {
greet(&Dog { name: String::from("Rex") });
greet(&Robot { model: String::from("R2-D2") });
}

실행 결과:

안녕하세요, Rex!
안녕하세요, R2-D2!
▶ 직접 실행해 보기

함수가 언제나 한 가지 타입만 다룬다면 제네릭이 아예 필요 없어요. fn greet(animal: &Dog)라고 쓰면 animal.name이 잘 동작해요. 트레이트를 함수 매개변수로 쓰기를 참고하세요.

() 없이 쓴 메서드. word.len이나 cart.total(total이 메서드일 때)이라고 쓰면 E0609와 비슷하지만, rustc는 다른 코드를 내요. E0615, “attempted to take value of method”(메서드의 값을 가져가려고 했다)이고, “use parentheses to call the method”(괄호를 써서 메서드를 호출하라)라는 힌트가 붙어요. Vec에 scores.len이라고 쓰면 E0616, “field len of struct Vec is private”(Vec 구조체의 len 필드는 비공개다)이 나와요. Vec에 정말로 그 이름의 숨은 필드가 있기 때문이에요. 둘 다 고치는 법은 같아요. word.len(), scores.len(), cart.total()처럼 쓰세요. 그리고 "hello".length는 &str에 대한 평범한 E0609예요. Rust에서는 이름이 len()이에요. 문자열을 참고하세요.

항목 하나가 아니라 Vec일 때. Vec<Dog>에 dogs.name이라고 쓰면 no field `name` on type `Vec<Dog>`가 나와요. 목록에는 name이 없고, 각 항목에 있어요. 해결: dogs[0].name으로 하나를 고르거나, for dog in &dogs { println!("{}", dog.name); }처럼 반복문을 쓰세요. 벡터를 참고하세요.

값이 아니라 Result일 때. load_config().port는 no field `port` on type `Result<Config, String>`를 내고, 똑같은 .unwrap() 힌트가 붙어요. 해결: Result를 반환하는 함수 안에서 let config = load_config()?;라고 쓰거나, match를 쓰세요. ? 연산자를 참고하세요.

숫자의 필드. u32에 age.value라고 쓰면 또 다른 코드가 나와요. E0610, “u32 is a primitive type and therefore doesn’t have fields”(u32는 기본 타입이라서 필드가 없다)예요. 숫자, bool, char에는 필드가 하나도 없어요. 아마 구조체 변수를 쓰려고 했을 거예요.

이해도 점검

1. 에러가 Option<User> 타입에 name 필드가 없다고 해요. 이게 무슨 뜻일까요?

2. struct Meters(f64); 타입의 값 m에서 첫 번째 필드는 어떻게 읽을까요?

3. fn show<T>(x: &T)에서 모든 T의 이름을 읽으려면 어떻게 해야 할까요?

점수: 0 / 3