課題7: 型システムの実践

マンダトリー要件

問題1:型の理解(20点)

以下の質問に答えなさい。

// 1-1: 以下の各変数の型を答えなさい
fn question_1() {
    let a = 42;
    let b = 3.14;
    let c = true;
    let d = 'A';
    let e = [1, 2, 3];
    let f = (1, "hello", true);
    let g = vec![1, 2, 3];
    let h = String::from("hello");
    let i = "world";
}

// 1-2: 以下のコードはコンパイルできるか?理由を説明しなさい
fn question_2() {
    let x = 5;
    let y = x;
    println!("{}, {}", x, y);
}

fn question_3() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}, {}", s1, s2);
}

// 1-3: 各型のメモリサイズを答えなさい(64bit環境)
// i8, i32, i64, f32, f64, bool, char
// &i32, String, Vec<i32>

// 1-4: 以下の型変換は成功するか?
fn question_4() {
    let x: i32 = 1000;
    let y: u8 = x as u8;  // yの値は?

    let a: f64 = 3.9;
    let b: i32 = a as i32; // bの値は?
}

各質問について:

  • 型を特定
  • 理由を説明
  • エラーの場合、修正方法を提示
  • 提出物problem1_type_analysis.md

    問題2:型変換ユーティリティ(20点)

    様々な型変換関数を実装しなさい。

    use std::num::ParseIntError;
    
    // 文字列を整数に変換
    fn parse_int(s: &str) -> Result<i32, ParseIntError> {
        // TODO: 実装
    }
    
    // 整数を文字列に変換
    fn int_to_string(n: i32) -> String {
        // TODO: 実装
    }
    
    // 摂氏を華氏に変換
    fn celsius_to_fahrenheit(celsius: f64) -> f64 {
        // TODO: 実装
        // 式: (celsius * 9/5) + 32
    }
    
    // 華氏を摂氏に変換
    fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 {
        // TODO: 実装
    }
    
    // バイト配列を文字列に変換
    fn bytes_to_string(bytes: Vec<u8>) -> Result<String, std::string::FromUtf8Error> {
        // TODO: 実装
    }
    
    // 文字列をバイト配列に変換
    fn string_to_bytes(s: String) -> Vec<u8> {
        // TODO: 実装
    }
    
    // 16進数文字列を整数に変換
    fn hex_to_int(hex: &str) -> Result<i32, ParseIntError> {
        // TODO: 実装
        // 例: "FF" -> 255
    }
    
    // 2進数文字列を整数に変換
    fn binary_to_int(binary: &str) -> Result<i32, ParseIntError> {
        // TODO: 実装
        // 例: "1010" -> 10
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_parse_int() {
            assert_eq!(parse_int("42").unwrap(), 42);
            assert_eq!(parse_int("-10").unwrap(), -10);
            assert!(parse_int("abc").is_err());
        }
    
        #[test]
        fn test_int_to_string() {
            assert_eq!(int_to_string(42), "42");
            assert_eq!(int_to_string(-10), "-10");
        }
    
        #[test]
        fn test_temperature_conversion() {
            assert_eq!(celsius_to_fahrenheit(0.0), 32.0);
            assert_eq!(celsius_to_fahrenheit(100.0), 212.0);
            assert_eq!(fahrenheit_to_celsius(32.0), 0.0);
        }
    
        #[test]
        fn test_bytes_conversion() {
            let bytes = vec![72, 101, 108, 108, 111];
            assert_eq!(bytes_to_string(bytes).unwrap(), "Hello");
    
            let s = String::from("World");
            assert_eq!(string_to_bytes(s), vec![87, 111, 114, 108, 100]);
        }
    
        #[test]
        fn test_hex_to_int() {
            assert_eq!(hex_to_int("FF").unwrap(), 255);
            assert_eq!(hex_to_int("10").unwrap(), 16);
        }
    
        #[test]
        fn test_binary_to_int() {
            assert_eq!(binary_to_int("1010").unwrap(), 10);
            assert_eq!(binary_to_int("1111").unwrap(), 15);
        }
    }
    

    提出物problem2_type_conversions.rs

    問題3:型安全なラッパー(20点)

    newtype パターンを使った型安全なラッパーを実装しなさい。

    // ユーザーID
    struct UserId(u64);
    
    impl UserId {
        fn new(id: u64) -> Self {
            // TODO: 実装
        }
    
        fn value(&self) -> u64 {
            // TODO: 実装
        }
    }
    
    // メールアドレス
    struct Email(String);
    
    impl Email {
        fn new(email: String) -> Result<Self, &'static str> {
            // TODO: 実装
            // 簡易バリデーション:@を含むかチェック
        }
    
        fn value(&self) -> &str {
            // TODO: 実装
        }
    
        fn domain(&self) -> Option<&str> {
            // TODO: 実装
            // @以降を返す
        }
    }
    
    // 年齢
    struct Age(u8);
    
    impl Age {
        fn new(age: u8) -> Result<Self, &'static str> {
            // TODO: 実装
            // 0-120の範囲チェック
        }
    
        fn value(&self) -> u8 {
            // TODO: 実装
        }
    
        fn is_adult(&self) -> bool {
            // TODO: 実装
            // 18歳以上かチェック
        }
    }
    
    // 金額(セント単位)
    struct Money(i64);
    
    impl Money {
        fn new(cents: i64) -> Self {
            // TODO: 実装
        }
    
        fn from_dollars(dollars: f64) -> Self {
            // TODO: 実装
        }
    
        fn cents(&self) -> i64 {
            // TODO: 実装
        }
    
        fn dollars(&self) -> f64 {
            // TODO: 実装
        }
    
        fn add(&self, other: &Money) -> Money {
            // TODO: 実装
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_user_id() {
            let id = UserId::new(123);
            assert_eq!(id.value(), 123);
        }
    
        #[test]
        fn test_email() {
            let email = Email::new("user@example.com".to_string()).unwrap();
            assert_eq!(email.value(), "user@example.com");
            assert_eq!(email.domain(), Some("example.com"));
    
            assert!(Email::new("invalid".to_string()).is_err());
        }
    
        #[test]
        fn test_age() {
            let age = Age::new(25).unwrap();
            assert_eq!(age.value(), 25);
            assert!(age.is_adult());
    
            let child = Age::new(10).unwrap();
            assert!(!child.is_adult());
    
            assert!(Age::new(150).is_err());
        }
    
        #[test]
        fn test_money() {
            let m1 = Money::new(100);
            assert_eq!(m1.cents(), 100);
            assert_eq!(m1.dollars(), 1.0);
    
            let m2 = Money::from_dollars(2.5);
            assert_eq!(m2.cents(), 250);
    
            let total = m1.add(&m2);
            assert_eq!(total.cents(), 350);
        }
    }
    

    提出物problem3_type_wrappers.rs

    問題4:データ構造の実装(20点)

    様々な型を使ったデータ構造を実装しなさい。

    // 座標
    #[derive(Debug, PartialEq)]
    struct Point {
        x: f64,
        y: f64,
    }
    
    impl Point {
        fn new(x: f64, y: f64) -> Self {
            // TODO: 実装
        }
    
        fn distance_from_origin(&self) -> f64 {
            // TODO: 実装
            // √(x² + y²)
        }
    
        fn distance_to(&self, other: &Point) -> f64 {
            // TODO: 実装
        }
    }
    
    // 長方形
    #[derive(Debug)]
    struct Rectangle {
        top_left: Point,
        width: f64,
        height: f64,
    }
    
    impl Rectangle {
        fn new(top_left: Point, width: f64, height: f64) -> Self {
            // TODO: 実装
        }
    
        fn area(&self) -> f64 {
            // TODO: 実装
        }
    
        fn perimeter(&self) -> f64 {
            // TODO: 実装
        }
    
        fn contains(&self, point: &Point) -> bool {
            // TODO: 実装
        }
    }
    
    // RGB色
    #[derive(Debug, PartialEq)]
    struct Color {
        r: u8,
        g: u8,
        b: u8,
    }
    
    impl Color {
        fn new(r: u8, g: u8, b: u8) -> Self {
            // TODO: 実装
        }
    
        fn from_hex(hex: &str) -> Result<Self, &'static str> {
            // TODO: 実装
            // 例: "FF0000" -> Color { r: 255, g: 0, b: 0 }
        }
    
        fn to_hex(&self) -> String {
            // TODO: 実装
        }
    
        fn brightness(&self) -> f64 {
            // TODO: 実装
            // (r + g + b) / 3
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_point() {
            let p1 = Point::new(3.0, 4.0);
            assert_eq!(p1.distance_from_origin(), 5.0);
    
            let p2 = Point::new(0.0, 0.0);
            assert_eq!(p1.distance_to(&p2), 5.0);
        }
    
        #[test]
        fn test_rectangle() {
            let rect = Rectangle::new(Point::new(0.0, 0.0), 10.0, 5.0);
            assert_eq!(rect.area(), 50.0);
            assert_eq!(rect.perimeter(), 30.0);
    
            assert!(rect.contains(&Point::new(5.0, 2.5)));
            assert!(!rect.contains(&Point::new(15.0, 2.5)));
        }
    
        #[test]
        fn test_color() {
            let red = Color::new(255, 0, 0);
            assert_eq!(red.to_hex(), "FF0000");
            assert_eq!(red.brightness(), 85.0);
    
            let blue = Color::from_hex("0000FF").unwrap();
            assert_eq!(blue, Color::new(0, 0, 255));
        }
    }
    

    提出物problem4_data_structures.rs

    ---

    ボーナス課題

    > ボーナス: 以下はオプションです。マンダトリー要件を完了してから挑戦してください。

    ボーナス1:単位変換ライブラリ(5点)

    様々な単位変換を実装しなさい。

    // 長さ
    struct Length {
        meters: f64,
    }
    
    impl Length {
        fn from_meters(meters: f64) -> Self {
            // TODO: 実装
        }
    
        fn from_kilometers(km: f64) -> Self {
            // TODO: 実装
        }
    
        fn from_miles(miles: f64) -> Self {
            // TODO: 実装
        }
    
        fn to_meters(&self) -> f64 {
            // TODO: 実装
        }
    
        fn to_kilometers(&self) -> f64 {
            // TODO: 実装
        }
    
        fn to_miles(&self) -> f64 {
            // TODO: 実装
        }
    }
    
    // 重さ
    struct Weight {
        grams: f64,
    }
    
    impl Weight {
        fn from_grams(grams: f64) -> Self {
            // TODO: 実装
        }
    
        fn from_kilograms(kg: f64) -> Self {
            // TODO: 実装
        }
    
        fn from_pounds(lbs: f64) -> Self {
            // TODO: 実装
            // 1ポンド = 453.592グラム
        }
    
        fn to_grams(&self) -> f64 {
            // TODO: 実装
        }
    
        fn to_kilograms(&self) -> f64 {
            // TODO: 実装
        }
    
        fn to_pounds(&self) -> f64 {
            // TODO: 実装
        }
    }
    

    提出物bonus1_unit_conversions/プロジェクト

    ボーナス2:日付・時刻の型(5点)

    安全な日付・時刻型を実装しなさい。

    #[derive(Debug, PartialEq)]
    struct Date {
        year: u16,
        month: u8,
        day: u8,
    }
    
    impl Date {
        fn new(year: u16, month: u8, day: u8) -> Result<Self, &'static str> {
            // TODO: バリデーション付き実装
        }
    
        fn is_leap_year(year: u16) -> bool {
            // TODO: 実装
        }
    
        fn days_in_month(&self) -> u8 {
            // TODO: 実装
        }
    
        fn next_day(&self) -> Date {
            // TODO: 実装
        }
    
        fn format(&self) -> String {
            // TODO: 実装
            // YYYY-MM-DD形式
        }
    }
    
    #[derive(Debug, PartialEq)]
    struct Time {
        hour: u8,
        minute: u8,
        second: u8,
    }
    
    impl Time {
        fn new(hour: u8, minute: u8, second: u8) -> Result<Self, &'static str> {
            // TODO: バリデーション付き実装
        }
    
        fn total_seconds(&self) -> u32 {
            // TODO: 実装
        }
    
        fn add_seconds(&self, seconds: u32) -> Time {
            // TODO: 実装
        }
    }
    

    提出物bonus2_datetime/プロジェクト

    ボーナス3:2D ベクトル演算(5点)

    2Dベクトルの演算を実装しなさい。

    #[derive(Debug, Clone, Copy, PartialEq)]
    struct Vector2D {
        x: f64,
        y: f64,
    }
    
    impl Vector2D {
        fn new(x: f64, y: f64) -> Self {
            // TODO: 実装
        }
    
        fn magnitude(&self) -> f64 {
            // TODO: 実装
        }
    
        fn normalize(&self) -> Vector2D {
            // TODO: 実装
        }
    
        fn dot(&self, other: &Vector2D) -> f64 {
            // TODO: 内積
        }
    
        fn add(&self, other: &Vector2D) -> Vector2D {
            // TODO: ベクトル加算
        }
    
        fn subtract(&self, other: &Vector2D) -> Vector2D {
            // TODO: ベクトル減算
        }
    
        fn scale(&self, scalar: f64) -> Vector2D {
            // TODO: スカラー倍
        }
    }
    

    提出物bonus3_vector2d/プロジェクト

    ボーナス4:有理数型(5点)

    分数を扱う有理数型を実装しなさい。

    #[derive(Debug, PartialEq)]
    struct Rational {
        numerator: i64,
        denominator: i64,
    }
    
    impl Rational {
        fn new(numerator: i64, denominator: i64) -> Result<Self, &'static str> {
            // TODO: 実装(分母0チェック、約分)
        }
    
        fn add(&self, other: &Rational) -> Rational {
            // TODO: 実装
        }
    
        fn multiply(&self, other: &Rational) -> Rational {
            // TODO: 実装
        }
    
        fn to_f64(&self) -> f64 {
            // TODO: 実装
        }
    
        fn gcd(a: i64, b: i64) -> i64 {
            // TODO: 最大公約数
        }
    
        fn reduce(&mut self) {
            // TODO: 約分
        }
    }
    

    提出物bonus4_rational/プロジェクト

    ボーナス5:型安全なビルダーパターン(5点)

    ビルダーパターンを実装しなさい。

    struct User {
        name: String,
        email: String,
        age: Option<u8>,
        address: Option<String>,
    }
    
    struct UserBuilder {
        name: Option<String>,
        email: Option<String>,
        age: Option<u8>,
        address: Option<String>,
    }
    
    impl UserBuilder {
        fn new() -> Self {
            // TODO: 実装
        }
    
        fn name(mut self, name: String) -> Self {
            // TODO: 実装
        }
    
        fn email(mut self, email: String) -> Self {
            // TODO: 実装
        }
    
        fn age(mut self, age: u8) -> Self {
            // TODO: 実装
        }
    
        fn address(mut self, address: String) -> Self {
            // TODO: 実装
        }
    
        fn build(self) -> Result<User, &'static str> {
            // TODO: 実装(nameとemailは必須)
        }
    }
    
    fn main() {
        let user = UserBuilder::new()
            .name("Alice".to_string())
            .email("alice@example.com".to_string())
            .age(30)
            .build()
            .unwrap();
    
        println!("{:?}", user);
    }
    

    提出物bonus5_builder_pattern/プロジェクト

    ---

    評価基準

    マンダトリー部分(80点)

    項目 配点 評価ポイント
    問題1:型の理解 20点 型システムの正確な理解
    問題2:型変換 20点 適切な変換処理
    問題3:型安全性 20点 newtype パターンの活用
    問題4:データ構造 20点 複合型の実装

    ボーナス部分(20点)

    項目 配点 評価ポイント
    ボーナス1:単位変換 5点 変換ロジック
    ボーナス2:日付・時刻 5点 バリデーション
    ボーナス3:ベクトル演算 5点 数学的正確性
    ボーナス4:有理数 5点 約分処理
    ボーナス5:ビルダー 5点 設計の質

    : ボーナスは最大20点まで加算されます。

    ---

    提出方法

    ファイル構成

    rust-foundations-07/
    ├── problem1_type_analysis.md
    ├── problem2_type_conversions.rs
    ├── problem3_type_wrappers.rs
    ├── problem4_data_structures.rs
    └── bonus/
        ├── bonus1_unit_conversions/
        ├── bonus2_datetime/
        ├── bonus3_vector2d/
        ├── bonus4_rational/
        └── bonus5_builder_pattern/
    

    提出期限

  • マンダトリー:第7章学習後、1週間以内
  • ボーナス:第10章修了時まで

---

ヒント

問題2のヒント

hex_to_int実装:

fn hex_to_int(hex: &str) -> Result<i32, ParseIntError> {
    i32::from_str_radix(hex, 16)
}

問題3のヒント

Email実装:

impl Email {
    fn new(email: String) -> Result<Self, &'static str> {
        if email.contains('@') {
            Ok(Email(email))
        } else {
            Err("Invalid email format")
        }
    }

    fn domain(&self) -> Option<&str> {
        self.0.split('@').nth(1)
    }
}

問題4のヒント

Color::from_hex実装:

fn from_hex(hex: &str) -> Result<Self, &'static str> {
    if hex.len() != 6 {
        return Err("Invalid hex length");
    }

    let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Invalid hex")?;
    let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Invalid hex")?;
    let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Invalid hex")?;

    Ok(Color { r, g, b })
}

---

学習の確認

この課題を通じて、以下を理解できたか確認してください:

  • [ ] プリミティブ型の種類とサイズ
  • [ ] 複合型(タプル、配列)の使い方
  • [ ] 型推論の仕組み
  • [ ] asキャストの使い方と注意点
  • [ ] From/Into トレイトの実装
  • [ ] newtype パターンの活用
  • [ ] Copy と Clone の違い

次の章では、制御構造を学びます。条件分岐、ループ、パターンマッチングについて理解を深めます。