콘텐츠로 이동

Rust 에러 E0063: missing field in initializer (구조체 필드 누락)

E0063은 Name { ... }로 구조체를 만드는데, 모든 필드에 값을 주지 않았다는 뜻이에요. Rust는 필드를 절대 대신 채워 주지 않아요. 그래서 0, false, None이라도 필드마다 값이 있어야 해요. 메시지가 빠진 필드를 이름으로 알려 주니까, 무엇을 추가해야 하는지 정확히 알 수 있어요. Rust에서 가장 친절한 에러 중 하나예요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.

error[E0063]: missing field `email` in initializer of `User`
--> src/main.rs:8:16
|
8 | let user = User {
| ^^^^ missing `email`

“missing field email in initializer of User“는 “User를 초기화하는 곳에 email 필드가 빠졌다”는 뜻이에요. 라벨 missing `email`은 “email이 없다”는 뜻이에요.

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

struct User {
name: String,
email: String,
active: bool,
}
fn main() {
let user = User {
name: String::from("Ferris"),
active: true,
// ❌ `email`이 없어요
};
println!("{}", user.name);
}

첫 줄은 빠진 필드(email)와 구조체(User)를 알려 줘요. ^^^^는 한 줄이 아니라 구조체를 만들기 시작한 구조체 이름을 가리켜요. 문제가 없는 무언가이기 때문이에요.

빠진 필드가 더 많으면 rustc는 알파벳 순서로 나열해요. 두세 개라면 전부 보여 줘요.

error[E0063]: missing fields `debug`, `retries` and `timeout_secs` in initializer of `Config`

“Config를 초기화하는 곳에 debug, retries, timeout_secs 필드가 빠졌다”는 뜻이에요.

네 개 이상이면 세 개만 보여 주고 나머지는 개수로 알려 줘요.

error[E0063]: missing fields `debug`, `port`, `retries` and 1 other field in initializer of `Config`
--> src/main.rs:10:18
|
10 | let config = Config {
| ^^^^^^ missing `debug`, `port`, `retries` and 1 other field

“1 other field”는 “그 밖의 필드 1개”라는 뜻이에요. 하나가 더 있다는 거죠. 전체 목록은 struct Config 정의에서 확인하세요.

모든 칸이 필수인 신청서와 비슷해요. 나중에 채우려고 했더라도 “이메일” 칸을 비워 둔 채로는 낼 수 없어요.

많은 언어에서는 빠진 필드가 아무 말 없이 null이나 0, 빈 문자열이 돼요. 그러면 이메일이 없는 사용자로 프로그램이 돌아가다가, 한참 떨어진 곳에서 고장 나요. 실제 서비스(production)에서 터지는 경우도 많아요. Rust에는 null이 없고, 몰래 값을 지어내지도 않아요. User에는 언제나 진짜 email이 있어요. 구조체 정의하고 만들기를 참고하세요.

그렇다고 매번 모든 필드를 직접 써야 한다는 뜻은 아니에요. 아래 해결 방법에서 보듯이, Rust에는 “기본값을 써 줘”나 “나머지는 이 값에서 복사해 줘”를 짧게 말하는 방법이 있어요. 핵심은 내가 고르고, 그 선택이 코드에 적혀 있다는 거예요.

대부분은 이 방법을 고르세요. 필드는 이유가 있어서 있는 거니까, 진짜 값을 주세요. 필드와 이름이 같은 변수가 있다면 User { name, email, active }처럼 축약법을 쓸 수 있어요.

struct User {
name: String,
email: String,
active: bool,
}
fn main() {
let user = User {
name: String::from("Ferris"),
email: String::from("[email protected]"), // ✅ 추가했어요
active: true,
};
println!("{} <{}> 활성: {}", user.name, user.email, user.active);
}

실행 결과:

Ferris <[email protected]> 활성: true
▶ 직접 실행해 보기

방법 2: Default를 derive하고 ..Default::default() 쓰기

섹션 제목: “방법 2: Default를 derive하고 ..Default::default() 쓰기”

설정이나 옵션처럼 필드가 많고, 대부분 뻔한 “빈” 값이 있을 때 이 방법을 고르세요. 구조체에 #[derive(Default)]를 붙이세요. 그런 다음 신경 쓰는 필드만 쓰고 ..Default::default()로 마무리하면 돼요. ..은 “내가 쓰지 않은 필드는 모두 이 값에서 가져와”라는 뜻이에요.

#[derive(Debug, Default)]
struct Config {
host: String,
port: u16,
debug: bool,
timeout_secs: u64,
retries: u8,
}
fn main() {
let config = Config {
host: String::from("localhost"),
port: 8080,
..Default::default() // ✅ debug, timeout_secs, retries는 기본값을 받아요
};
println!("{:?}", config);
}

실행 결과:

Config { host: "localhost", port: 8080, debug: false, timeout_secs: 0, retries: 0 }
▶ 직접 실행해 보기

derive한 기본값은 숫자는 0, bool은 false, String은 빈 문자열, Option은 None이에요. 0이 나쁜 기본값이라면(타임아웃 0초는 아마 그렇겠죠) 더 나은 값으로 impl Default for Config를 직접 쓰세요. Default: 적당한 시작 값을 참고하세요.

#[derive(Default)]를 깜빡하면 에러가 E0277로 바뀌어요. the trait bound `Config: Default` is not satisfied(Config가 Default 트레이트 바운드를 만족하지 않는다)와 함께 help: consider annotating `Config` with `#[derive(Default)]`(Config에 #[derive(Default)]를 붙여 보라)가 나와요.

여러 곳에서 구조체를 만들거나, 어떤 필드가 늘 같은 값으로 시작할 때 이 방법을 고르세요. 구조체 리터럴 전체를 함수 하나에 넣고, 다른 곳에서는 그 함수를 호출하세요. 나중에 필드를 추가해도 호출하는 곳 전부가 아니라 new만 고치면 돼요.

struct Player {
name: String,
score: u32,
level: u8,
}
impl Player {
fn new(name: &str) -> Player {
Player {
name: name.to_string(),
score: 0, // 새 플레이어는 모두 0점으로...
level: 1, // ...1레벨에서 시작해요
}
}
}
fn main() {
let ana = Player::new("Ana");
let mut bo = Player::new("Bo");
bo.score += 50;
println!("{}: 점수 {}, 레벨 {}", ana.name, ana.score, ana.level);
println!("{}: 점수 {}, 레벨 {}", bo.name, bo.score, bo.level);
}

실행 결과:

Ana: 점수 0, 레벨 1
Bo: 점수 50, 레벨 1
▶ 직접 실행해 보기

new 안의 구조체 리터럴에도 모든 필드가 필요하니까, 빠뜨린 필드는 그곳에서 E0063으로 나타나요. 괜찮아요. 한 번만 고치면 되니까요. 생성자 스타일의 함수를 참고하세요.

방법 4: ..other로 나머지를 다른 값에서 복사하기

섹션 제목: “방법 4: ..other로 나머지를 다른 값에서 복사하기”

비슷한 값이 이미 있고, 몇 가지만 바꾼 복사본이 필요할 때 이 방법을 고르세요. ..base는 내가 쓰지 않은 필드를 모두 base에서 가져와요.

#[derive(Debug, Clone)]
struct Theme {
name: String,
font_size: u8,
dark: bool,
}
fn main() {
let base = Theme { name: String::from("Default"), font_size: 14, dark: false };
// `name`과 `dark`만 바뀌고, 나머지는 `base`에서 와요
let night = Theme { name: String::from("Night"), dark: true, ..base };
// `..base`는 base에서 `name`을 이동시키니까, base를 계속 쓰려면 clone해요
let large = Theme { font_size: 20, ..base.clone() };
println!("{:?}", night);
println!("{:?}", large);
println!("{:?}", base);
}

실행 결과:

Theme { name: "Night", font_size: 14, dark: true }
Theme { name: "Default", font_size: 20, dark: false }
Theme { name: "Default", font_size: 14, dark: false }
▶ 직접 실행해 보기

알아 둘 점이 하나 있어요. ..base는 String 같은 필드를 base에서 이동시켜요. night에서는 괜찮아요. name을 직접 썼으니까 Copy 필드(font_size)만 base에서 왔거든요. large에서는 name이 base에서 오게 되니까, base를 계속 쓸 수 있도록 ..base.clone()을 써요. clone을 빼고 나중에 base를 쓰면 E0382가 나요.

정말로 비어 있을 수 있는 필드도 있어요. 사용자가 아직 알려 주지 않은 전화번호, 별명, 종료 날짜 같은 것들이요. 그런 필드는 Option으로 만드세요. 그래도 직접 써 줘야 하지만(Rust가 None을 대신 채워 주지 않아요), 이제 None은 솔직하고 허용된 값이에요. Option을 참고하세요.

#[derive(Debug, Default)]
struct Contact {
name: String,
email: String,
phone: Option<String>, // 없을 수도 있어요
nickname: Option<String>,
}
fn main() {
let a = Contact {
name: String::from("Ana"),
email: String::from("[email protected]"),
phone: None, // 직접 썼어요: "아직 전화번호 없음"
nickname: Some(String::from("Annie")),
};
// Default를 쓰면, 생략한 Option은 모두 None이 돼요
let b = Contact { name: String::from("Bo"), ..Default::default() };
println!("{} 전화번호: {:?}, 별명: {:?}", a.name, a.phone, a.nickname);
println!("{} 이메일: {:?}, 전화번호: {:?}", b.name, b.email, b.phone);
}

실행 결과:

Ana 전화번호: None, 별명: Some("Annie")
Bo 이메일: "", 전화번호: None
▶ 직접 실행해 보기

b.email이 ""인 것을 눈여겨보세요. 이게 Default의 함정이에요. 꼭 필요한 필드가 아무 말 없이 비어 버려요. Option은 정말로 선택적인 것에만 쓰고, email처럼 필수인 필드는 평범한 타입으로 두고 항상 채우세요.

구조체에 필드를 추가한 직후. User에 created_at을 추가했더니, 갑자기 프로젝트 안의 모든 User { ... }에서 E0063이 나요. Rust가 도와주는 거예요. 그 하나하나가 생각해 봐야 할 곳이니까요. 생성자 new(방법 3)를 쓰면 이 목록이 짧아져요.

이름 있는 필드를 가진 열거형 배리언트. Shape::Rectangle { width: 2.0 }은 missing field `height` in initializer of `Shape`를 내요. 메시지에 배리언트가 아니라 열거형 이름이 나온다는 점에 주의하세요. 해결: 그 배리언트의 모든 필드에 값을 주세요. 열거형은 데이터를 담을 수 있어요를 참고하세요.

구조체를 반환하는 함수. fn new_player(name: &str) -> Player { Player { name: name.to_string(), score: 0 } }에서는 화살표가 함수 안의 Player를 가리키고 missing `level`이 나와요. 해결: 그 리터럴에 level을 추가하세요.

  • E0560: struct has no field named x: 반대 경우예요. 구조체에 없는 필드를 썼어요. 오타 하나로 두 에러가 동시에 나는 경우가 많아요.
  • E0559: variant has no field named x: 같은 이름 실수를 열거형 배리언트에서 한 경우예요.
  • E0277: trait bound not satisfied: ..Default::default()를 썼는데 구조체가 Default를 derive하지 않았어요.
  • E0451: field is private: 구조체가 다른 모듈에 있고 일부 필드가 비공개라서 채울 수 없어요. 비공개 필드를 빠뜨리면 E0063이 아니라 “cannot construct Account with struct literal syntax due to private fields”(비공개 필드 때문에 구조체 리터럴 문법으로 Account를 만들 수 없다)가 나요. 어느 쪽이든 모듈이 제공하는 new 함수를 쓰세요.
  • E0382: use of moved value: ..value가 필드를 이동시킨 뒤에 그 값을 썼어요.
  • 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. rustc가 missing fields a, b, c and 2 other fields라고 해요. 빠진 필드는 몇 개일까요?

2. 구조체에 아직 필요 없는 Option<String> 필드가 있어요. 그 필드에는 무엇을 쓸까요?

3. Config { port: 80, ..Default::default() }가 컴파일되려면 무엇이 필요할까요?

점수: 0 / 3