Rust 에러 E0451: field of struct is private (구조체 리터럴의 비공개 필드)
E0451은 Account { ... } 같은 리터럴로 구조체를 만들려고 했는데, 필드 중 하나가 비공개(private)라는 뜻이에요. 구조체 리터럴을 쓰려면 모든 필드를 채워야 하기 때문에, 비공개 필드가 하나만 있어도 문이 잠겨요. 일부러 그런 거예요. 모듈은 내가 모듈이 준비한 생성자를 거쳐 가기를 바라는 거죠. 해결책은 거의 항상 new 함수를 호출하는 것(또는 만드는 것)이에요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.
에러 메시지
섹션 제목: “에러 메시지”error[E0451]: field `balance` of struct `Account` is private --> src/main.rs:21:9 |19 | let account = bank::Account { | ------------- in this type20 | owner: String::from("Ferris"),21 | balance: 1_000_000, // ❌ 비공개 필드 | ^^^^^^^ private field“구조체 Account의 필드 balance는 비공개”라는 뜻이에요. in this type은 “이 타입에서”, private field는 “비공개 필드”예요.
이 에러를 내는 프로그램은 다음과 같아요.
mod bank { pub struct Account { pub owner: String, balance: i64, // 비공개 }
impl Account { pub fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }
pub fn balance(&self) -> i64 { self.balance } }}
fn main() { let account = bank::Account { owner: String::from("Ferris"), balance: 1_000_000, // ❌ 비공개 필드 }; println!("{} has {}", account.owner, account.balance());}비슷한 친척 에러를 대신 볼 수도 있어요.
| 내가 쓴 코드 | rustc가 하는 말 |
|---|---|
| 리터럴에 비공개 필드를 넣음 | error[E0451]: field `balance` of struct `Account` is private |
Config { verbose: true, ..Default::default() } |
이것도 E0451. ..Default::default()를 가리켜요(방법 3 참고) |
| 비공개 필드를 빼고 리터럴을 씀 | cannot construct `Account` with struct literal syntax due to private fields, 그리고 자주 help: you might have meant to use the `new` associated function |
튜플 구조체에 Percent(150) |
E0603: tuple struct constructor `Percent` is private |
세 번째 줄은 “비공개 필드가 있어서 구조체 리터럴 문법으로 Account를 만들 수 없다”는 뜻이고, help는 “연관 함수 new를 쓰려던 게 아니냐”고 물어요. 모두 고치는 방법은 같아요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”화살표는 내가 값을 넣으면 안 되는 필드, balance를 가리켜요. owner는 pub이라서 넣어도 괜찮아요. 하지만 구조체 리터럴은 전부 아니면 전무예요. balance를 빼먹을 수도 없고(Rust는 빠진 필드를 채워 주지 않아요), 값을 넣을 수도 없어요. 그래서 mod bank 밖에서는 Account { ... }를 쓸 방법이 아예 없어요.
진짜 은행 계좌를 여는 걸 떠올려 보세요. 신청서에 내 시작 잔액을 직접 적을 수는 없어요. 이름을 적으면 은행이 계좌를 만들고, 잔액이 0에서 시작한다는 건 은행이 정해요. Account::new가 바로 그 신청서예요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”구조체 필드는 함수나 모듈처럼 기본적으로 비공개예요(모듈 참고). 바깥 코드가 원하는 잔액으로 Account를 마음대로 만들 수 있다면, 모듈의 규칙은 아무 의미가 없을 거예요. 누군가 백만 원짜리 계좌나 마이너스 50원짜리 계좌를 만들 수 있겠죠. 리터럴은 mod bank만 쓸 수 있기 때문에, 프로그램의 모든 Account는 규칙을 아는 코드가 만들어요.
좋은 점이 하나 더 있어요. 나중에 created_at이나 history 같은 비공개 필드를 추가해도 모듈 밖의 코드는 깨지지 않아요. 그 코드는 리터럴을 쓴 적이 없고 new만 호출하니까, new 한 곳만 고치면 돼요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: pub fn new 생성자 쓰기(또는 추가하기)
섹션 제목: “방법 1: pub fn new 생성자 쓰기(또는 추가하기)”대부분은 이 방법을 고르세요. 먼저 new(또는 from_..., with_...) 함수가 있는지 찾아보세요. rustc가 알려 줄 때도 많아요. 내가 만든 구조체인데 생성자가 없다면 하나 만드세요.
mod bank { pub struct Account { pub owner: String, balance: i64, // 비공개 }
impl Account { // 모든 계좌는 0에서 시작해요. 돈은 deposit()으로만 들어와요. pub fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }
pub fn deposit(&mut self, amount: i64) { if amount > 0 { self.balance += amount; } }
pub fn balance(&self) -> i64 { self.balance } }}
fn main() { let mut account = bank::Account::new("Ferris"); account.deposit(250); println!("{}의 잔액: {}", account.owner, account.balance());}실행 결과:
Ferris의 잔액: 250mod bank 안에서는 리터럴을 쓸 수 있으니, new는 마음껏 Account { ... }를 쓸 수 있어요. new 자체에 pub을 붙이는 걸 잊지 마세요. 없으면 E0624(associated function `new` is private, 연관 함수 new는 비공개)가 나요. 받으면 안 되는 입력이 있다면 new가 Option<Account>나 Result<Account, String>을 반환하게 하세요. E0603 페이지에 예제가 있어요. 생성자에 대해서는 구조체에서 더 볼 수 있어요.
방법 2: 단순한 데이터라면 필드를 pub으로
섹션 제목: “방법 2: 단순한 데이터라면 필드를 pub으로”어떤 값의 조합이든 괜찮을 때 이 방법을 고르세요. 색상은 숫자 세 개일 뿐이고, 막아야 할 “잘못된” 색상은 없어요. 이럴 때는 리터럴이 가장 알기 쉬운 방법이에요.
mod theme { pub struct Color { pub r: u8, pub g: u8, pub b: u8, }}
fn main() { let ferris_orange = theme::Color { r: 247, g: 76, b: 0 }; println!("#{:02x}{:02x}{:02x}", ferris_orange.r, ferris_orange.g, ferris_orange.b);}실행 결과:
#f74c00모든 필드에 pub이 필요해요. 내 크레이트에서만 만들 수 있으면 된다면 필드에 pub(crate)를 붙여도 돼요.
필드를 비공개로 둬야 할 때: 값이 잘못될 수 있을 때(음수 잔액, 100을 넘는 퍼센트, @가 없는 이메일), 또는 두 필드가 서로 맞아야 할 때(items의 합과 같아야 하는 total)예요. 이럴 때 리터럴을 허용하면 확인을 건너뛸 수 있게 되니, 방법 1이 맞아요.
방법 3: Default로 시작해서 공개 필드 바꾸기
섹션 제목: “방법 3: Default로 시작해서 공개 필드 바꾸기”적당한 기본값이 있는 “설정” 구조체에서, 일부 필드는 공개이고 일부는 비공개일 때 이 방법을 고르세요. 먼저 함정부터 볼게요. 구조체 갱신 문법 ..Default::default()는 될 것처럼 보이지만, 안 돼요.
mod settings { pub struct Config { pub verbose: bool, retries: u32, // 비공개: 1에서 10 사이여야 해요 }
impl Default for Config { fn default() -> Config { Config { verbose: false, retries: 3 } } }}
fn main() { let config = settings::Config { verbose: true, ..Default::default() }; println!("{}", config.verbose);}error[E0451]: field `retries` of struct `Config` is private --> src/main.rs:15:54 |15 | let config = settings::Config { verbose: true, ..Default::default() }; | ^^^^^^^^^^^^^^^^^^ field `retries` is private“필드 retries는 비공개”라는 뜻이에요. ..Default::default()도 여전히 구조체 리터럴이에요. retries를 새 구조체로 복사하는데, 그것도 값을 넣는 걸로 쳐요. 대신 기본값을 먼저 만들고, 그다음에 공개 필드를 바꾸세요.
mod settings { pub struct Config { pub verbose: bool, retries: u32, // 비공개: 1에서 10 사이여야 해요 }
impl Default for Config { fn default() -> Config { Config { verbose: false, retries: 3 } } }
impl Config { pub fn retries(&self) -> u32 { self.retries } }}
fn main() { let mut config = settings::Config::default(); config.verbose = true; // 공개 필드는 나중에 바꿀 수 있어요 println!("verbose: {}, retries: {}", config.verbose, config.retries());}실행 결과:
verbose: true, retries: 3모든 필드가 pub이라면 ..Default::default()도 잘 동작해요. 막는 건 비공개 필드뿐이에요.
방법 4: 빌더 스타일 메서드 제공하기
섹션 제목: “방법 4: 빌더 스타일 메서드 제공하기”옵션이 여러 개이고 그중 일부에 규칙이 있을 때 이 방법을 고르세요. 각 메서드는 구조체를 받아 설정 하나를 바꾸고(확인도 하면서) 구조체를 다시 돌려줘요. 그래서 호출을 깔끔하게 이어 쓸 수 있어요.
mod settings { pub struct Config { verbose: bool, retries: u32, }
impl Config { pub fn new() -> Config { Config { verbose: false, retries: 3 } }
pub fn verbose(mut self, on: bool) -> Config { self.verbose = on; self }
// 규칙은 여기에 있어요. 재시도는 1번에서 10번까지예요. pub fn retries(mut self, n: u32) -> Config { self.retries = n.clamp(1, 10); self }
pub fn describe(&self) -> String { format!("verbose: {}, retries: {}", self.verbose, self.retries) } }}
fn main() { let config = settings::Config::new().verbose(true).retries(50); println!("{}", config.describe());}실행 결과:
verbose: true, retries: 10재시도를 50번 달라고 했지만 조용히 10번이 됐어요. 규칙이 한 곳에 있기 때문이에요. 많은 크레이트가 이렇게 하고, 때로는 ConfigBuilder 같은 별도 타입과 마지막 .build() 호출을 써요. 그 방식은 디자인 패턴에서 볼 수 있어요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”구조체를 모듈로 옮긴 뒤. main.rs의 User { name, email }은 User를 mod user(또는 src/user.rs)로 옮기기 전까지는 다 잘 동작했어요. 이제는 전부 실패해요. 구조체마다 정하세요. 단순한 데이터라면 필드에 pub을 붙이고(방법 2), 규칙이 있다면 pub fn new를 만드세요(방법 1).
표준 라이브러리나 크레이트의 타입. Vec { ... }이나 Duration { ... }은 쓸 수 없어요. rustc는 cannot construct `Vec<_, _>` with struct literal syntax due to private fields(비공개 필드 때문에 구조체 리터럴로 만들 수 없음)라고 하면서 Vec::new()나 Vec::with_capacity(_) 같은 함수를 보여 줘요. 그런 함수나 Duration::from_secs(5)를 쓰세요.
다른 크레이트의 #[non_exhaustive] 구조체. 이 경우에는 필드가 공개여도 소용없고, 에러 코드도 달라요. error[E0639]: cannot create non-exhaustive struct using struct expression(non-exhaustive 구조체는 구조체 표현식으로 만들 수 없음)이에요. 그 크레이트는 나중에 필드를 추가해도 내 코드가 깨지지 않기를 바라는 거예요. 방법 1, 방법 3과 똑같이 생성자나 Default를 쓰세요.
관련 에러
섹션 제목: “관련 에러”- E0616: 같은 비공개 필드지만, 그 필드를 읽을 때 나요(
account.balance). - E0603:
tuple struct constructor `X` is private, 그리고 비공개 함수, 구조체, 모듈. - E0624:
associated function `new` is private. 생성자에도pub이 필요해요. - E0560:
struct `X` has no field named `y`. 리터럴의 필드 이름에 오타가 있어요. - E0063 (영어):
missing field `x` in initializer of `Y`. 공개 필드를 빼먹었어요. - 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. mod tickets 안에 pub struct Ticket { pub title: String, id: u32 }가 있어요. main에서 Ticket을 어떻게 만들 수 있을까요?
2. Config에 비공개 필드가 있을 때 Config { verbose: true, ..Default::default() }는 왜 실패할까요?
3. 필드를 모두 pub으로 두고 리터럴로 만들기에 알맞은 구조체는 무엇일까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0451 — Rust error codes index (영어): 짧은 예제가 있는 이 에러의 공식 설명이에요.
- 경로를 사용하여 모듈 트리의 아이템 참조하기 — The Rust Programming Language 한국어판: 비공개 필드가 있어서 공개 생성자가 필요한 구조체를 보여 줘요.
- Struct visibility — Rust by Example (영어): 공개 필드와 비공개 필드가 있는 구조체를 모듈 밖에서 만드는 예제예요.
- Builder pattern — Rust Design Patterns (영어): 별도의 빌더 타입을 쓰는 빌더 패턴 전체를 설명해요.
Default트레이트 — 표준 라이브러리 문서 (영어): 기본값을 derive하거나 직접 구현하는 법을 알 수 있어요.