プライベートフィールド
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 10/61。
前回のレッスンでは、構造体(struct)、そのフィールド、およびそのメソッドなど、すべてを公開(public)にしました。しかし、真のカプセル化とは、データがどのようにアクセスされるかを制御することを意味します。Rustでは、構造体を公開しつつ、そのフィールドを非公開(private)に保つことができます。
残高が負にならないようにすべきWalletを考えてみましょう。もしフィールドがパブリックであれば、誰でも任意の値を設定できてしまいます。
// wallet.rs
pub struct Wallet {
balance: f64, // pubがない = プライベート!
}
impl Wallet {
pub fn new(initial: f64) -> Wallet {
Wallet { balance: initial }
}
}
Wallet はパブリックですが、balance には pub キーワードがないことに注意してください。これは、モジュールの外にあるコードがコンストラクタを使用して Wallet を作成できる一方で、balance フィールドを直接読み取ったり変更したりすることはできないことを意味します:
// main.rs
mod wallet;
use wallet::Wallet;
fn main() {
let w = Wallet::new(100.0);
// println!("{}", w.balance); // エラー!フィールドはプライベートです
// w.balance = -50.0; // エラー!プライベートなフィールドにはアクセスできません
}
これがカプセル化の基礎です。struct は自らのデータを制御します。外部のコードは提供されたメソッドを経由する必要があり、これによりバリデーションやロギング、その他のロジックを追加できるようになります。データは不正な状態から保護されたままになります。
今後のレッスンでは、ゲッターおよびセッターメソッドを通じて、このプライベートデータへの制御されたアクセスを提供する方法を学びます。
チャレンジ
簡単機密データをモジュール内に保護してカプセル化を実証する、温度センサーシステムを構築しましょう。
コードを整理するために、2つのファイルを作成します。
sensor.rs:location(String) とreading(f64) の2つのフィールドを持つTemperatureSensor構造体を定義します。この構造体は他のファイルから使用できるようにパブリック(public)にする必要がありますが、両方のフィールドはプライベート(private)のままにしてください。外部のコードがセンサーのデータに直接アクセスしたり変更したりできないようにするためです。場所の名前と初期温度の読み取り値を受け取る、パブリックなnewコンストラクタを追加してください。main.rs: センサーモジュールを取り込み、コンストラクタを使用してTemperatureSensorを作成します。フィールドがプライベートであるため、それらを直接出力することはできません。代わりに、センサーが正常に作成されたことを示す確認メッセージを出力してください。
このチャレンジでは、プライベートフィールドの核心的な概念に焦点を当てています。構造体自体にはアクセス可能ですが、その内部データは保護されています。外部のコードは、提供されたメソッド(この場合はコンストラクタのみ)を通じてのみセンサーとやり取りできます。
出力は次の形式に従う必要があります:
Sensor created for: {location}フィールド自体がプライベートであるため、出力用に場所にアクセスするには、new 関数がセンサーを返す前にこのメッセージを出力するようにする必要があります。
例えば、場所 "Kitchen"、読み取り値 22.5 でセンサーを作成した場合、出力は次のようになります:
Sensor created for: Kitchen場所の名前と初期温度の読み取り値の2つの入力を受け取ります。
チートシート
Rustでは、カプセル化を実現するために、構造体を公開(public)にしつつ、そのフィールドを非公開(private)に保つことができます。これにより、外部のコードが内部データに直接アクセスしたり、変更したりするのを防ぐことができます。
フィールドを非公開にするには、単に pub キーワードを省略します:
pub struct Wallet {
balance: f64, // pubなし = 非公開フィールド
}
impl Wallet {
pub fn new(initial: f64) -> Wallet {
Wallet { balance: initial }
}
}
この例では、Wallet は公開されています(モジュールの外で使用可能)が、balance は非公開です(外部から直接アクセスできません)。
外部のコードは公開されたコンストラクタを使用してインスタンスを作成できますが、非公開フィールドを読み取ったり変更したりすることはできません:
mod wallet;
use wallet::Wallet;
fn main() {
let w = Wallet::new(100.0);
// println!("{}", w.balance); // エラー!フィールドは非公開です
// w.balance = -50.0; // エラー!非公開フィールドにはアクセスできません
}
このカプセル化により、構造体が自身のデータを制御することが保証され、外部のコードは提供されたメソッドを使用してデータを操作する必要があります。
自分で試してみよう
mod sensor;
use sensor::TemperatureSensor;
fn main() {
// 入力を読み取る
let mut location = String::new();
std::io::stdin().read_line(&mut location).expect("Failed to read line");
let location = location.trim().to_string();
let mut reading_str = String::new();
std::io::stdin().read_line(&mut reading_str).expect("Failed to read line");
let reading: f64 = reading_str.trim().parse().expect("Failed to parse reading");
// TODO: コンストラクタを使用してTemperatureSensorを作成する
// new関数が確認メッセージの出力を処理します
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。