🦀

Rustの勉強 5日目

2022/10/01に公開

The Rust Programming Language 日本語版 第5章

構造体について。

5.1 構造体を定義し、インスタンス化する

  • Rustの構造体はGoと似ているので覚えやすい。
    • 構造体名はキャメルケース
    • フィールド名: 型名, で記述していく。フィールド名は小文字。
    • フィールドへのアクセスはピリオド
    • 一部のフィールドのみをミュータブルにすることはできない。
struct StructName {
    field1: String,
    field2: u64,
    field3: bool,
}
  • 初期化省略記法や構造体更新記法はちょっとはまりそうだしあんまり使いたくない
  • タプル構造体は「名前付きタプル」みたいな感じで使いやすそう
    • struct Color(i32, i32, i32) と宣言方法も割とわかりやすい
  • なにもないタプルに名前を付けたものがユニット様構造体と呼ばれる

5.2 構造体を使ったプログラム例

println! マクロで表示するためには std::fmt::Display トレイトを実装していないといけないけれど、それが満たせない場合には #[derive(Debug)] 注釈を足して、println! マクロのフォーマット文字列内で {:?} もしくは {:#?} を使って表示できる。

5.3 メソッド記法

  • 構造体はメソッドを持てて、 impl 構造体名 のブロックの中で関数を宣言する。
    • 第1引数は self として、これがメソッドのレシーバーとなる。もちろんほしいものに合わせて &self&mut self にできる。
  • 構造体には self を引数に取らない関連関数を作れて、これでコンストラクタとかを用意できる。個人的にはGoの慣習に頼った書き方よりは好き。(Goだと NewXXX() 関数を用意するが、慣習でしかない)
  • implブロックは複数あってもよい

Rustlings

structs のセクションが第5章に相当するようなのでやっていく。

structs1.rs

構造体はとても素直なのですんなりできた。

struct ColorClassicStruct {
    red: u32,
    green: u32,
    blue: u32,
}

struct ColorTupleStruct(u32, u32, u32);

#[derive(Debug)]
struct UnitLikeStruct();

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn classic_c_structs() {
        // TODO: Instantiate a classic c struct!
        let green = ColorClassicStruct {
            red: 0,
            green: 255,
            blue: 0,
        };

        assert_eq!(green.red, 0);
        assert_eq!(green.green, 255);
        assert_eq!(green.blue, 0);
    }

    #[test]
    fn tuple_structs() {
        // TODO: Instantiate a tuple struct!
        let green = ColorTupleStruct(0, 255, 0);

        assert_eq!(green.0, 0);
        assert_eq!(green.1, 255);
        assert_eq!(green.2, 0);
    }

    #[test]
    fn unit_structs() {
        // TODO: Instantiate a unit-like struct!
        let unit_like_struct = UnitLikeStruct();
        let message = format!("{:?}s are fun!", unit_like_struct);

        assert_eq!(message, "UnitLikeStructs are fun!");
    }
}

structs2.rs

構造体更新法、こういう場合には便利なんだなーと理解した。

#[derive(Debug)]
struct Order {
    name: String,
    year: u32,
    made_by_phone: bool,
    made_by_mobile: bool,
    made_by_email: bool,
    item_number: u32,
    count: u32,
}

fn create_order_template() -> Order {
    Order {
        name: String::from("Bob"),
        year: 2019,
        made_by_phone: false,
        made_by_mobile: false,
        made_by_email: true,
        item_number: 123,
        count: 0,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn your_order() {
        let order_template = create_order_template();
        // TODO: Create your own order using the update syntax and template above!
        let your_order = Order {
            name: String::from("Hacker in Rust"),
            count: 1,
            ..order_template
        };
        assert_eq!(your_order.name, "Hacker in Rust");
        assert_eq!(your_order.year, order_template.year);
        assert_eq!(your_order.made_by_phone, order_template.made_by_phone);
        assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile);
        assert_eq!(your_order.made_by_email, order_template.made_by_email);
        assert_eq!(your_order.item_number, order_template.item_number);
        assert_eq!(your_order.count, 1);
    }
}

structs3.rs

構造体のメソッドの実装は、この問題のレベルでは非常に素直だったので、逆に何もはまらなかった。もしここで &mut self みたいなものじゃないといけない問題だったらはまるかも知れない。

#[derive(Debug)]
struct Package {
    sender_country: String,
    recipient_country: String,
    weight_in_grams: i32,
}

impl Package {
    fn new(sender_country: String, recipient_country: String, weight_in_grams: i32) -> Package {
        if weight_in_grams <= 0 {
            panic!("Can not ship a weightless package.")
        } else {
            Package {
                sender_country,
                recipient_country,
                weight_in_grams,
            }
        }
    }

    fn is_international(&self) -> bool {
        self.sender_country != self.recipient_country
    }

    fn get_fees(&self, cents_per_gram: i32) -> i32 {
        self.weight_in_grams * cents_per_gram
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn fail_creating_weightless_package() {
        let sender_country = String::from("Spain");
        let recipient_country = String::from("Austria");

        Package::new(sender_country, recipient_country, -2210);
    }

    #[test]
    fn create_international_package() {
        let sender_country = String::from("Spain");
        let recipient_country = String::from("Russia");

        let package = Package::new(sender_country, recipient_country, 1200);

        assert!(package.is_international());
    }

    #[test]
    fn create_local_package() {
        let sender_country = String::from("Canada");
        let recipient_country = sender_country.clone();

        let package = Package::new(sender_country, recipient_country, 1200);

        assert!(!package.is_international());
    }

    #[test]
    fn calculate_transport_fees() {
        let sender_country = String::from("Spain");
        let recipient_country = String::from("Spain");

        let cents_per_gram = 3;

        let package = Package::new(sender_country, recipient_country, 1500);

        assert_eq!(package.get_fees(cents_per_gram), 4500);
    }
}

Discussion