コンテンツにスキップ

Rustのエラー E0616: field of struct is private(非公開のフィールド)

E0616 は、指定したフィールドはちゃんとあるけれど、構造体を定義したモジュールの中でしか使えない(非公開の)フィールドだった という意味のエラーです。構造体そのものを持つことはできても、中身に直接手を伸ばすことはできません。構造体を初めて自分のモジュールに移したときや、v.len() のつもりで v.len と書いたときに、誰でも一度は出会います。たいていはメソッドを呼ぶだけで直ります。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0616]: field `balance` of struct `Account` is private
--> src/main.rs:16:50
|
16 | println!("{} has {}", account.owner, account.balance); // ❌ private field
| ^^^^^^^ private field

「field balance of struct Account is private」は「構造体 Account のフィールド balance は非公開」という意味です。private field のラベルが、その非公開のフィールドを指しています。

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

mod bank {
pub struct Account {
pub owner: String,
balance: u64, // private
}
impl Account {
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
}
}
fn main() {
let account = bank::Account::new("Ferris");
println!("{} has {}", account.owner, account.balance); // ❌ private field
}

同じエラーが、いくつかの形で表示されます。

書いたコード rustc の表示 直し方
account.balance field `balance` of struct `Account` is private 直し方2か直し方3
Vec の scores.len field `len` of struct `Vec` is private と help: a method `len` also exists, call it with parentheses 直し方1
Duration の timeout.secs field `secs` of struct `Duration` is private 直し方1(as_secs())
account.balance = 500 同じ E0616。書き込みも禁止されている 直し方2

2行目の help は「len というメソッドもあるので、かっこを付けて呼んでください」という意味です。

矢印は、ドットのあとのフィールド名を指しています。同じ行の account.owner は問題ありません。owner には pub が付いているからです。balance には付いていないので、読んだり書き換えたりできるのは mod bank の中のコードだけです。

Account そのものは pub になっていることに注目してください。多くの人が驚くのはここです。構造体を公開しても、フィールドは公開されません。 フィールドは1つずつ pub を付けるか、非公開のままかのどちらかです。

銀行を思い浮かべてみてください。銀行には誰でも入れますし、口座の名義は誰にでも見えます。でも、カウンターの奥に入って金庫を開け、お金を数えることはできません。窓口の人に頼むと、その人が銀行のルールに従って対応してくれます。

Rust では、何もしなければすべてが非公開です。関数も、モジュールも、構造体のフィールドもです(モジュールを見てください)。公開・非公開は型ではなくモジュールで決まります。mod bank の中のコードなら balance をさわれますが、外のコードはさわれません。

こうする目的は、構造体が自分のルールを守れるようにすることです。たとえば「銀行の残高は決してマイナスにならない」というルールがあるとします。もしプログラムのどこからでも account.balance = ... と書けたら、どこでもこのルールを破れてしまいます。そして、どこが破ったのかを探すには、コード全体を調べなければなりません。フィールドが非公開なら、それを変更できるのは mod bank の中の少しのメソッドだけです。そこでルールをチェックすれば、プログラム全体でルールが守られます。この考え方をカプセル化(encapsulation)と呼びます。

標準ライブラリも同じことをしています。Vec は、長さとメモリのバッファを非公開のフィールドとして持っています。もし要素が3つしかないベクタに v.len = 100 と書けたら、v[50] はでたらめなメモリを読んでしまいます。len が非公開だからこそ、Vec は安全に使えるのです。

直し方1:フィールドを読む代わりにメソッドを呼ぶ

Section titled “直し方1:フィールドを読む代わりにメソッドを呼ぶ”

構造体が標準ライブラリやほかのクレートのものなら、これを選びます。そのコードは変更できませんが、見てよいものには必ずメソッドが用意されています。rustc が help: a method ... also exists(メソッドもある)と表示したら、() を付けましょう。

use std::time::Duration;
fn main() {
let scores = vec![10, 20, 30];
println!("スコアは{}件", scores.len());
let timeout = Duration::from_secs(90);
println!("タイムアウト: {}秒", timeout.as_secs());
}

実行結果:

スコアは3件
タイムアウト: 90秒
▶ 実際に試してみよう

メソッド名がフィールド名と同じとは限りません(secs は as_secs() で読みます)。docs.rs でその型のページを見るか、ドットを打ったあとにエディタが出してくれるメソッドの一覧を見てみましょう。

直し方2:ゲッターと、ルールを守るメソッドを追加する

Section titled “直し方2:ゲッターと、ルールを守るメソッドを追加する”

自分の構造体で、フィールドに守るべきルールがあるときは、これを選びます。フィールドは非公開のままにして、値を返す pub fn(ゲッター)を追加します。値を変えるのは、ルールをチェックするメソッドを通したときだけにします。

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
}
pub fn deposit(&mut self, amount: i64) {
if amount > 0 {
self.balance += amount;
}
}
// ルールはここにある:残高は決して0を下回らない。
pub fn withdraw(&mut self, amount: i64) -> Result<(), String> {
if amount > self.balance {
return Err(format!("残高は{}しかありません", self.balance));
}
self.balance -= amount;
Ok(())
}
}
}
fn main() {
let mut account = bank::Account::new("Ferris");
account.deposit(100);
println!("{}の残高: {}", account.owner, account.balance());
match account.withdraw(500) {
Ok(()) => println!("500を引き出しました"),
Err(e) => println!("500は引き出せません: {e}"),
}
println!("残高は{}のまま", account.balance());
}

実行結果:

Ferrisの残高: 100
500は引き出せません: 残高は100しかありません
残高は100のまま
▶ 実際に試してみよう

Rust では、メソッドとフィールドに同じ名前を付けられます。そのため、ゲッターの名前は(get_balance() ではなく)balance() にするのがふつうです。ゲッターがあれば、rustc もそれを提案してくれます。help: a method `balance` also exists, call it with parentheses(balance というメソッドもあるので、かっこを付けて呼んでください)。deposit と withdraw はセッターの役目をします。フィールドを変更しますが、ルールが許す形でしか変えません。メソッドについて詳しくは構造体を見てください。

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

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

どんな値でもかまわなくて、ほかに何もそのフィールドに頼っていないときは、これを選びます。座標の点、色、設定の構造体などには、守るべきルールがありません。そういうものにゲッターを書いても、手間が増えるだけです。

mod geometry {
pub struct Point {
pub x: i32,
pub y: i32,
}
}
fn main() {
let mut p = geometry::Point { x: 3, y: 4 };
p.x += 1;
println!("点の位置: ({}, {})", p.x, p.y);
}

実行結果:

点の位置: (4, 4)
▶ 実際に試してみよう

フィールドを自分のコードだけに見せたいなら、pub(crate) x: i32(このクレートのどのモジュールからでも)や pub(super) x: i32(親モジュールからだけ)と書きます。プログラムの中ではこれらは pub と同じように動きますが、ライブラリでは、そのフィールドを公開 API から外しておけます。すべての段階をまとめた表が、E0603 のページにあります。

とりあえず pub にしてしまっていい? 自分にこう聞いてみましょう。「このフィールドがある値になったら、あるいはこのフィールドだけが変わったら、構造体がおかしな状態になることはある?」 マイナスの残高、データと合わない len、項目の合計と一致しなくなった total。こういうものは非公開にして、直し方2を使いましょう。答えが「いいえ、どんな値でも大丈夫」なら、pub にするのが素直な選択です。

Vec の len。 scores.len と書いて field `len` of struct `Vec` is private が出た場合です。Vec には本当に len という非公開のフィールドがあるので、こうなります。直し方は scores.len() です。おもしろいことに、String で name.len と書くと、別のエラー E0615(attempted to take value of method、メソッドの値を取ろうとした)になります。String にはその名前のフィールドがないからです。直し方は同じです。

外からフィールドに値を入れるとき。 account.balance = 500; も同じ E0616 になります。書き込みも禁止されているのです。値を入れるためだけにフィールドを pub にするのはやめましょう。deposit のように、変更とチェックをまとめて行うメソッドを追加します。

構造体を自分のモジュールやファイルに移したあと。 main.rs のコードは、何か月も user.email を使っていました。ところが User を src/user.rs に移したとたん、フィールドへのアクセスがすべてエラーになります。前は問題なかったフィールドが、mod user の中だけの非公開のものになったからです。ただのデータのフィールドには pub を付け、それ以外にはゲッターを用意しましょう。

テスト。 同じファイルの中にある #[cfg(test)] mod tests ブロックは子モジュールなので、非公開のフィールドも読めます。tests/ フォルダのテストはクレートの外にあるので、ほかの利用者と同じく pub の API しか使えません。

  • E0603:function `x` is private(関数 x は非公開)。関数、構造体、モジュールについての同じ考え方です。
  • E0451:同じ非公開のフィールドですが、Account { ... } で構造体を作るときに出ます。
  • E0624:method `x` is private(メソッド x は非公開)。メソッド(や new)にも pub が必要です。
  • E0609:no field `x` on type `T` (型 T にフィールド x がない)。そもそもフィールドが存在しない(よくあるのはタイプミス)。
  • E0615:attempted to take value of method(メソッドの値を取ろうとした)。x.len() のつもりで x.len と書いた。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. mod bank の中に pub struct Account { balance: u64 } があります。main から account.balance を読めますか?

2. `field len of struct Vec is private` が出ました。どう直しますか?

3. Score 構造体の points フィールドは、0から100の間でなければなりません。いちばんよい設計はどれですか?

スコア: 0 / 3