Menu
Coddy logo textTech

Option Enum の再訪

CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 17/61。

RustですでにOption<T>に遭遇したことがあるでしょう。これは、存在するかもしれないし、存在しないかもしれない値を表現するための標準的な列挙型(enum)です。それでは、OOP(オブジェクト指向プログラミング)の視点からこれを見てみましょう。Optionは単なる列挙型ではなく、ヌル(nullable)になり得る値を安全かつエレガントに扱うための強力な組み込みメソッドを備えた型なのです。

毎回マッチングを行う代わりに、Option 値に対して直接メソッドを使用できます。unwrap_or メソッドは、値が存在すればその内部の値を返し、存在しなければ指定したデフォルト値を返します:

let maybe_name: Option<String> = Some(String::from("Alice"));
let name = maybe_name.unwrap_or(String::from("Guest"));
println!("{}", name);  // Alice

let empty: Option<String> = None;
let fallback = empty.unwrap_or(String::from("Guest"));
println!("{}", fallback);  // Guest

mapメソッドは、内部の値が存在する場合にその値を変換し、Noneの場合はそのままにします。これにより、常にNoneをチェックすることなく、操作を連鎖させることができます:

let maybe_num: Option<i32> = Some(5);
let doubled = maybe_num.map(|x| x * 2);
println!("{:?}", doubled);  // Some(10)

let nothing: Option<i32> = None;
let still_nothing = nothing.map(|x| x * 2);
println!("{:?}", still_nothing);  // None

これらのメソッドはOOPの原則を体現しています。Option型は「値があるかもしれない」という概念をカプセル化し、それを扱うためのクリーンなインターフェースを提供します。それを検査するための外部ロジックを記述するのではなく、オブジェクトに対してメソッドを呼び出します。

challenge icon

チャレンジ

簡単

Option メソッドを使用して、オプションデータを適切に処理するユーザープロファイルシステムを構築しましょう。ユーザーはニックネームを設定している場合もあれば、設定していない場合もあります。あなたのシステムは、明示的なマッチングを行わずに、両方のケースをエレガントに処理します。

コードを整理するために、2つのファイルを作成します:

  • profile.rs: 2つのフィールド、username (String) と nickname (Option<String>) を持つ公開 Profile 構造体を定義します。以下を実装してください:
    • ユーザー名とオプションのニックネームを受け取る new コンストラクタ
    • ニックネームが存在する場合はそれを返し、存在しない場合はフォールバックとしてユーザー名を返す display_name メソッド。これを処理するには unwrap_or を使用してください。
    • ニックネームが存在する場合はブラケットで囲まれたニックネーム(例:"[CoolNick]")を含む Option<String> を返し、ニックネームがない場合は None を返す formatted_nickname メソッド。値を変換するには map を使用してください。
  • main.rs: プロファイルモジュールを取り込み、ニックネームがあるプロファイルとないプロファイルの2つを作成します。各プロファイルで両方のメソッドを実行し、Option メソッドが値の有無をどのように異なって処理するかを示してください。

formatted_nickname メソッドは Option を返すため、出力する際はデバッグフォーマット ({:?}) を使用してください。

出力は、両方のプロファイルを示す以下の正確な形式に従う必要があります:

Display name: {name}
Formatted nickname: {option}
Display name: {name}
Formatted nickname: {option}

例えば、ユーザー名が "alice" でニックネームが "Ace" のプロファイルと、ユーザー名が "bob" でニックネームがない別のプロファイルの場合、出力は以下のようになります:

Display name: Ace
Formatted nickname: Some("[Ace]")
Display name: bob
Formatted nickname: None

3つの入力を受け取ります:1つ目のユーザー名、1つ目のユーザーのニックネーム、そして2つ目のユーザー名(ニックネームなし)です。

チートシート

Option<T>型は、明示的なマッチングを行わずにオプション値を操作するためのメソッドを提供します。

unwrap_orメソッドは、値が存在すればその内部の値を返し、Noneであればデフォルト値を返します。

let maybe_name: Option<String> = Some(String::from("Alice"));
let name = maybe_name.unwrap_or(String::from("Guest"));
println!("{}", name);  // Alice

let empty: Option<String> = None;
let fallback = empty.unwrap_or(String::from("Guest"));
println!("{}", fallback);  // Guest

mapメソッドは、値が存在すればその内部の値を変換し、Noneの場合はそのままにします:

let maybe_num: Option<i32> = Some(5);
let doubled = maybe_num.map(|x| x * 2);
println!("{:?}", doubled);  // Some(10)

let nothing: Option<i32> = None;
let still_nothing = nothing.map(|x| x * 2);
println!("{:?}", still_nothing);  // None

これらのメソッドを使用すると、常にNoneをチェックすることなく、Option値に対して操作を連鎖させることができます。

自分で試してみよう

mod profile;

use profile::Profile;

fn main() {
    // 入力を読み込む
    let mut input1 = String::new();
    std::io::stdin().read_line(&mut input1).expect("Failed to read line");
    let username1 = input1.trim().to_string();

    let mut input2 = String::new();
    std::io::stdin().read_line(&mut input2).expect("Failed to read line");
    let nickname1 = input2.trim().to_string();

    let mut input3 = String::new();
    std::io::stdin().read_line(&mut input3).expect("Failed to read line");
    let username2 = input3.trim().to_string();

    // TODO: username1 と nickname1 (Some) を使用して1つ目のプロファイルを作成する
    
    // TODO: username2 を使用し、ニックネームなし (None) で2つ目のプロファイルを作成する
    
    // TODO: 1つ目のプロファイルの display_name と formatted_nickname を表示する
    // formatted_nickname は Option を返すため、{:?} を使用すること
    
    // TODO: 2つ目のプロファイルの display_name と formatted_nickname を表示する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン