Lewati ke konten

Error Rust E0451: field of struct is private (field privat saat membuat struct)

E0451 artinya: kamu mencoba membuat struct dengan literal seperti Account { ... }, tapi salah satu field-nya privat. Untuk menulis struct literal, kamu harus mengisi setiap field, jadi satu field privat saja sudah cukup untuk menguncimu di luar. Ini disengaja: modulnya ingin kamu lewat constructor miliknya sendiri. Perbaikannya hampir selalu memanggil (atau menambahkan) fungsi new. Halaman ini bagian dari Penjelasan error compiler Rust.

error[E0451]: field `balance` of struct `Account` is private
--> src/main.rs:21:9
|
19 | let account = bank::Account {
| ------------- in this type
20 | owner: String::from("Ferris"),
21 | balance: 1_000_000, // ❌ field privat
| ^^^^^^^ private field

Compiler bilang field balance milik struct Account itu privat. Label in this type menunjuk struct yang sedang kamu buat, dan ^^^^^^^ menunjuk field yang tidak boleh kamu isi.

Ini program yang menghasilkan error tersebut:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privat
}
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, // ❌ field privat
};
println!("{} has {}", account.owner, account.balance());
}

Kamu mungkin juga melihat salah satu “saudara dekat”-nya:

Apa yang kamu tulis Apa kata rustc
field privat di dalam literal error[E0451]: field `balance` of struct `Account` is private
Config { verbose: true, ..Default::default() } juga E0451, menunjuk ke ..Default::default() (lihat Perbaikan 3)
literal tanpa field privatnya cannot construct `Account` with struct literal syntax due to private fields (tidak bisa membuat Account dengan struct literal karena ada field privat), sering dengan help: you might have meant to use the `new` associated function
Percent(150) untuk tuple struct E0603: tuple struct constructor `Percent` is private

Semuanya punya perbaikan yang sama.

Panahnya menunjuk field yang tidak boleh kamu isi: balance. owner sudah pub, jadi mengisinya tidak masalah. Tapi struct literal itu semua atau tidak sama sekali. Kamu tidak bisa melewatkan balance (Rust tidak mengisi field yang hilang), dan kamu juga tidak boleh mengisinya. Jadi di luar mod bank, sama sekali tidak ada cara untuk menulis Account { ... }.

Bayangkan membuka rekening bank sungguhan. Kamu tidak boleh menulis sendiri saldo awalmu di formulir. Kamu mengisi namamu, bank membuatkan rekeningnya, dan bank yang menentukan bahwa saldonya mulai dari nol. Account::new adalah formulir itu.

Field struct privat secara default, sama seperti fungsi dan modul (lihat Modul). Kalau kode di luar bisa membuat Account dengan saldo sesukanya, aturan modul itu tidak ada artinya. Orang bisa membuat rekening berisi satu juta, atau minus lima puluh. Karena hanya mod bank yang boleh menulis literal-nya, setiap Account di program dibuat oleh kode yang tahu aturannya.

Ada keuntungan kedua. Kalau nanti kamu menambahkan field privat, seperti created_at atau history, kode di luar modul tidak rusak, karena kode itu tidak pernah menulis literal-nya. Kode itu memanggil new, dan kamu cukup memperbarui new di satu tempat.

Perbaikan 1: Pakai (atau tambahkan) constructor pub fn new

Section titled “Perbaikan 1: Pakai (atau tambahkan) constructor pub fn new”

Pilih ini hampir setiap saat. Cari dulu fungsi new (atau from_..., with_...). rustc sering menunjukkannya kepadamu. Kalau struct-nya milikmu sendiri dan belum ada, tulis satu:

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // privat
}
impl Account {
// Setiap rekening mulai dari 0. Uang hanya masuk lewat 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!("Saldo {}: {}", account.owner, account.balance());
}

Output:

Saldo Ferris: 250
▶ Coba sendiri

Di dalam mod bank literal-nya diizinkan, jadi new bebas menulis Account { ... }. Jangan lupa pub pada new itu sendiri; tanpa itu kamu mendapat E0624 (associated function `new` is private). Kalau ada input yang harus ditolak, biarkan new mengembalikan Option<Account> atau Result<Account, String>; halaman E0603 menunjukkan contohnya. Selengkapnya tentang constructor di Struct.

Perbaikan 2: Jadikan field-nya pub kalau datanya sederhana

Section titled “Perbaikan 2: Jadikan field-nya pub kalau datanya sederhana”

Pilih ini kalau kombinasi nilai apa pun boleh. Sebuah warna hanyalah tiga angka; tidak ada warna yang “salah” yang perlu dicegah. Dalam kasus ini, literal adalah cara paling jelas untuk membuatnya:

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);
}

Output:

#f74c00
▶ Coba sendiri

Kamu butuh pub di setiap field. Kalau hanya crate-mu sendiri yang boleh membuatnya, pub(crate) pada field-nya juga bisa.

Kapan sebaiknya field-nya tetap privat: saat sebuah nilai bisa salah (saldo negatif, persentase di atas 100, email tanpa @), atau saat dua field harus saling cocok (total yang harus sama dengan jumlah items). Di situ literal akan membiarkan orang melewati pemeriksaannya, dan Perbaikan 1 adalah pilihan yang tepat.

Perbaikan 3: Mulai dari Default, lalu ubah field publiknya

Section titled “Perbaikan 3: Mulai dari Default, lalu ubah field publiknya”

Pilih ini untuk struct “pengaturan” yang punya nilai default masuk akal, dengan sebagian field publik dan sebagian tidak. Pertama, ini jebakannya. Sintaks struct update ..Default::default() kelihatannya seharusnya bisa, tapi ternyata tidak:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privat: harus tetap di antara 1 dan 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

Compiler bilang field retries privat, dan menunjuk ke ..Default::default().

..Default::default() tetaplah struct literal: ia menyalin retries ke struct yang baru, dan itu dihitung sebagai mengisi field tersebut. Sebagai gantinya, buat dulu nilai default-nya, lalu ubah field publiknya sesudahnya:

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // privat: harus tetap di antara 1 dan 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; // field publik boleh diubah sesudahnya
println!("verbose: {}, retries: {}", config.verbose, config.retries());
}

Output:

verbose: true, retries: 3
▶ Coba sendiri

Kalau setiap field pub, ..Default::default() bisa dipakai tanpa masalah. Hanya field privat yang menghalanginya.

Perbaikan 4: Sediakan method bergaya builder

Section titled “Perbaikan 4: Sediakan method bergaya builder”

Pilih ini kalau ada beberapa opsi dan sebagian punya aturan. Setiap method menerima struct-nya, mengubah satu pengaturan (sambil memeriksanya), lalu mengembalikan struct itu, sehingga pemanggilannya bisa dirangkai dengan rapi:

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
}
// Aturannya ada di sini: 1 sampai 10 kali percobaan.
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());
}

Output:

verbose: true, retries: 10
▶ Coba sendiri

Permintaan 50 kali percobaan diam-diam menjadi 10, karena aturannya tinggal di satu tempat. Banyak crate melakukan ini, kadang dengan tipe ConfigBuilder terpisah dan panggilan .build() di akhir. Lihat Design pattern untuk versi itu.

Setelah memindahkan struct ke dalam modul. Setiap User { name, email } di main.rs berjalan lancar sampai User dipindahkan ke mod user (atau src/user.rs). Sekarang semuanya gagal. Putuskan per struct: data sederhana mendapat field pub (Perbaikan 2); apa pun yang punya aturan mendapat pub fn new (Perbaikan 1).

Tipe dari standard library atau crate lain. Kamu tidak bisa menulis Vec { ... } atau Duration { ... }. rustc bilang cannot construct `Vec<_, _>` with struct literal syntax due to private fields dan mendaftar fungsi seperti Vec::new() dan Vec::with_capacity(_). Pakai fungsi-fungsi itu, atau Duration::from_secs(5).

Struct #[non_exhaustive] dari crate lain. Di sini field publik pun tidak menolong, dan kodenya berbeda: error[E0639]: cannot create non-exhaustive struct using struct expression (struct non-exhaustive tidak bisa dibuat dengan struct expression). Crate itu ingin bisa menambah field nanti tanpa merusak kodemu, jadi pakai constructor-nya atau Default, persis seperti Perbaikan 1 dan Perbaikan 3.

  • E0616: field privat yang sama, tapi saat membaca-nya (account.balance).
  • E0603: tuple struct constructor `X` is private, dan fungsi, struct, atau modul yang privat.
  • E0624: associated function `new` is private. Constructor-mu juga butuh pub.
  • E0560: struct `X` has no field named `y` . Salah ketik nama field di dalam literal.
  • E0063 (bahasa Inggris): missing field `x` in initializer of `Y` . Kamu melewatkan sebuah field publik.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. pub struct Ticket { pub title: String, id: u32 } ada di dalam mod tickets. Bagaimana main bisa membuat Ticket?

2. Kenapa Config { verbose: true, ..Default::default() } gagal kalau Config punya field privat?

3. Struct mana yang cocok untuk field yang semuanya pub dan literal biasa?

Skor: 0 / 3