コンテンツにスキップ

Rustのエラー E0451: field of struct is private(構造体リテラルの非公開フィールド)

E0451 は、Account { ... } のような構造体リテラルで構造体を作ろうとしたけれど、フィールドの中に非公開のものがあった という意味のエラーです。構造体リテラルを書くには、すべてのフィールドを埋めなければなりません。そのため、非公開のフィールドが1つあるだけで締め出されてしまいます。これはわざとそうなっています。モジュールは、自分のコンストラクタを通して作ってほしいのです。直し方は、ほとんどの場合 new 関数を呼ぶ(なければ追加する)ことです。このシリーズのほかのエラーは、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, // ❌ private field
| ^^^^^^^ private field

「field balance of struct Account is private」は「構造体 Account のフィールド balance は非公開」という意味です。in this type は「この型の」、private field は「非公開のフィールド」というラベルです。

このエラーが出るのは、次のプログラムです。

mod bank {
pub struct Account {
pub owner: String,
balance: i64, // private
}
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, // ❌ private field
};
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

3行目は「非公開のフィールドがあるので、構造体リテラルの構文では Account を作れない」という意味で、help は「関連関数 new を使うつもりだったのでは」と提案しています。4行目は「タプル構造体のコンストラクタ Percent は非公開」という意味です。どれも直し方は同じです。

矢印は、設定してはいけないフィールド balance を指しています。owner は pub なので、設定してかまいません。でも、構造体リテラルは「全部か、ゼロか」です。balance を書かずに済ませることはできませんし(Rust は足りないフィールドを埋めてくれません)、書くこともできません。つまり mod bank の外では、Account { ... } と書く方法がそもそもないのです。

実際の銀行で口座を開くところを想像してみてください。申込書に、最初の残高を自分で書き込むことはできません。名前を書けば銀行が口座を作り、最初の残高はゼロだと銀行が決めます。Account::new は、その申込書にあたります。

構造体のフィールドは、関数やモジュールと同じく、何もしなければ非公開です(モジュールを見てください)。もし外のコードが好きな残高で Account を作れたら、モジュールのルールは意味がなくなります。残高100万円の口座も、マイナス50円の口座も作れてしまいます。リテラルを書けるのが mod bank だけなので、プログラムの中のすべての Account は、ルールを知っているコードによって作られます。

もう1つ利点があります。あとから created_at や history のような非公開のフィールドを追加しても、モジュールの外のコードは壊れません。外のコードはリテラルを書いていないからです。呼んでいるのは new なので、new を1か所直すだけで済みます。

直し方1:pub fn new コンストラクタを使う(なければ追加する)

Section titled “直し方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の残高: 250
▶ 実際に試してみよう

mod bank の中ではリテラルが使えるので、new の中では Account { ... } を自由に書けます。new そのものに pub を付けるのを忘れないでください。付けないと E0624(associated function `new` is private、関連関数 new は非公開)になります。受け付けてはいけない入力があるなら、new が Option<Account> や Result<Account, String> を返すようにしましょう。E0603 のページにその例があります。コンストラクタについて詳しくは構造体を見てください。

直し方2:ただのデータなら、フィールドを pub にする

Section titled “直し方2:ただのデータなら、フィールドを pub にする”

値がどんな組み合わせでもかまわないときは、これを選びます。色はただの3つの数字で、守るべき「間違った色」はありません。それなら、リテラルで作るのがいちばんわかりやすい方法です。

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を超えるパーセント、@ のないメールアドレス)や、2つのフィールドが互いに合っていなければならないとき(items の合計と一致すべき total)です。この場合、リテラルを許すとチェックを飛ばされてしまうので、直し方1が正解です。

直し方3:Default から始めて、公開のフィールドを変える

Section titled “直し方3:Default から始めて、公開のフィールドを変える”

ちょうどいいデフォルト値のある「設定」の構造体で、公開のフィールドと非公開のフィールドが混ざっているときは、これを選びます。先に落とし穴を見ておきましょう。構造体更新記法の ..Default::default() は動きそうに見えますが、動きません。

mod settings {
pub struct Config {
pub verbose: bool,
retries: u32, // private: must stay between 1 and 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

「構造体 Config のフィールド retries は非公開」という意味です(コード中のコメントは「非公開:1から10の間でなければならない」です)。..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:ビルダー風のメソッドを用意する

Section titled “直し方4:ビルダー風のメソッドを用意する”

オプションがいくつもあって、その中にルールのあるものが混ざっているときは、これを選びます。それぞれのメソッドは構造体を受け取り、設定を1つだけ(チェックしながら)変えて、構造体を返します。そのため、呼び出しをきれいにつなげて書けます。

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回になりました。ルールが1か所にまとまっているからです。多くのクレートがこの方法を使っていて、別の 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<_, _> を作れない)と表示し、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(タプル構造体のコンストラクタ X は非公開)。非公開の関数、構造体、モジュールも。
  • E0624:associated function `new` is private(関連関数 new は非公開)。コンストラクタにも pub が必要です。
  • E0560:struct `X` has no field named `y` (構造体 X に y というフィールドはない)。リテラルのフィールド名のタイプミスです。
  • E0063(英語):missing field `x` in initializer of `Y` (Y の初期化にフィールド x がない)。公開のフィールドを書き忘れた。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. mod tickets の中に pub struct Ticket { pub title: String, id: u32 } があります。main から Ticket を作るにはどうしますか?

2. Config に非公開のフィールドがあるとき、Config { verbose: true, ..Default::default() } がエラーになるのはなぜですか?

3. すべてのフィールドを pub にして、そのままリテラルで作るのに向いている構造体はどれですか?

スコア: 0 / 3