0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Effective-Rustlings-jp】Day 1:型システムを使ってデータ構造を再現しよう

Last updated at Posted at 2025-01-01

はじめに

こんにちは。細々とプログラミングをしているsotanengelです。
この記事は以下の記事の連載です。

他の連載記事 (詳細)

また本記事はEffective Rust(David Drysdale (著), 中田 秀基 (翻訳))を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください!

今日の内容

概要

Rustの強みとして、強力なデータ型がいくつも標準で実装されています。
これらを使いこなすことでコンパイル時にエラーを発見し、開発速度を向上させましょう。

適切な型を選択して、暗黙的な型変換を防止しよう

xはi32型です。
それよりもサイズの大きいi64型の変数yxを代入するにはどうすればいいでしょうか?

fn main() {
    let x: i32 = 42;
    let y: i64 = x; // TODO: 抜けているキーワードを追加しましょう。
    println!("{}", y);
}

解答

サイズが明らかに大きい変数への代入でもRustでは厳格なルールのもとに型を変換します。
したがって、Rustでは変数の型を厳密に定義することでバグの混入を防ぐ確率が高くなります。

fn main() {
    let x: i32 = 42;
    let y: i64 = x.into(); // into で型変換を行います。
    println!("{}", y);
}

Enumで関数への代入を正確に行う

問(リンク)

コンパイルエラーが起きている部分を修正しましょう。

コード (詳細)
enum Apple {
    Price(u32),
}

enum Meat {
    Price(u32),
}

// AppleとMeatを引数にとる関数
fn print_price(apple: Apple, meat: Meat) {
    match apple {
        Apple::Price(price) => println!("Apple price: {} yen", price),
    }
    match meat {
        Meat::Price(price) => println!("Meat price: {} yen", price),
    }
}

fn main() {
    let apple = Apple::Price(300);
    let meat = Meat::Price(1200);

    print_price(meat, apple); // TODO: Enumを使うことであるエラーをコンパイル時に特定できます。
}

解答(リンク)

Enumで異なるデータを定義することで、コンパイル時に引数の渡し方のミスを検知することができます。

コード (詳細)
enum Apple {
    Price(u32),
}

enum Meat {
    Price(u32),
}

// AppleとMeatを引数にとる関数
fn print_price(apple: Apple, meat: Meat) {
    match apple {
        Apple::Price(price) => println!("Apple price: {} yen", price),
    }
    match meat {
        Meat::Price(price) => println!("Meat price: {} yen", price),
    }
}

fn main() {
    let apple = Apple::Price(300);
    let meat = Meat::Price(1200);

    print_price(apple, meat); // Enumを使うことで引数の順番のミスを事前に検知できます。
}

Enumにmatchを使って抜け漏れを防止しよう

問(リンク)

Enumに対するmatch文のエラーを解消してください。

コード (詳細)
enum TrafficLight {
    Red,
    Yellow,
    Green,
    Blinking,
}

fn signal_action(light: TrafficLight) {
    match light {
        // TODO: コンパイルエラーを解消してください。
        TrafficLight::Red => println!("停止してください"),
        TrafficLight::Green => println!("進んでください"),
    }
}

fn main() {
    let light = TrafficLight::Yellow;
    signal_action(light);
}

解答(リンク)

Enumとmatchを組み合わせることで、条件の抜け漏れをコンパイル時に検知することができます。

コード (詳細)
#[allow(dead_code)]
enum TrafficLight {
    Red,
    Yellow,
    Green,
    Blinking,
}

// 信号の状態に応じてアクションを決定する関数
fn signal_action(light: TrafficLight) {
    match light {
        TrafficLight::Red => println!("停止してください"), // 赤信号では停止
        TrafficLight::Yellow => println!("注意してください"), // 黄色信号では注意
        TrafficLight::Green => println!("進んでください"), // 緑信号では進行
        TrafficLight::Blinking => println!("点滅中 - 注意して進んでください"), // 点滅信号の処理を追加
    }
}

fn main() {
    let light = TrafficLight::Yellow;
    signal_action(light); // 黄色信号をテスト
}

構造体とEnumを組み合わせよう

問(リンク)

TrafficLightという型を使って、コンパイルエラーを解消してください。

コード (詳細)
// 信号の状態を表すEnum
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

// 車両を表す構造体
// TODO:先ほどまでの問題のTrafficLightを参考にcurrent_lightに適切な型を挿入しなさい。
struct Vehicle {
    model: String,
    current_light: String, // 信号状態を保持するフィールド("Red"か"Yellow"、"Green"が入る)
}

// 信号の状態に応じて車両のアクションを決める関数
fn drive(vehicle: &Vehicle) {
    match vehicle.current_light {
        TrafficLight::Red => println!("{} は停止しています。", vehicle.model),
        TrafficLight::Yellow => println!("{} は注意しています。", vehicle.model),
        TrafficLight::Green => println!("{} は進んでいます。", vehicle.model),
    }
}

fn main() {
    // 車両インスタンスを作成し、信号を設定
    let car = Vehicle {
        model: String::from("Toyota Prius"),
        current_light: "Red", // 赤信号
    };

    drive(&car); // 実行すると「Toyota Prius は停止しています。」と表示
}

解答(リンク)

Enumとstructを用いることで、構造体のフィールドの要素を柔軟に表現することができます。
特に今回の場合、問題文ではcurrent_lightフィールドにコメントでフィールド条件を記載していましたが、このようなコードの管理方法ではコメントの管理が行われなくなった時にバグが混入する原因となります。
Rustの優秀な型表現を使って、バグの混入を防ぎましょう。

コード (詳細)
// 信号の状態を表すEnum
#[allow(dead_code)]
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

// 車両を表す構造体
struct Vehicle {
    model: String,
    current_light: TrafficLight, // 信号状態をTrafficLightというEnumで定義することで「有効な値の組み合わせ」だけを格納できるようにしよう。
}

// 信号の状態に応じて車両のアクションを決める関数
fn drive(vehicle: &Vehicle) {
    match vehicle.current_light {
        TrafficLight::Red => println!("{} は停止しています。", vehicle.model),
        TrafficLight::Yellow => println!("{} は注意しています。", vehicle.model),
        TrafficLight::Green => println!("{} は進んでいます。", vehicle.model),
    }
}

fn main() {
    // 車両インスタンスを作成し、信号を設定
    let car = Vehicle {
        model: String::from("Toyota Prius"),
        current_light: TrafficLight::Red, // 赤信号
    };

    drive(&car); // 実行すると「Toyota Prius は停止しています。」と表示
}

さいごに

もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?