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?

The Rust Programming Languageを読み進める(5章)

Posted at

はじめに

The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。

5章:構造体を使用して関係のあるデータを構造化する

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

構造体の基礎

fn main() {
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    println!("{}", user1.email);
    user1.email = String::from("anotheremail@example.com");
    println!("{}", user1.email);
}

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
  • 構造体にはデータの名前と型を定義し、セットでフィールドと呼ぶ
  • user1の部分はインスタンスを生成している
  • 構造体のフィールドは書き換え可能だが、一部のフィールドのみ可変にすることは不可
fn main() {
    let email = String::from("test@example.com");
    let username = String::from("some user");

    let user = build_user(email, username);
    println!("email: {}, user: {}", user.email, user.username);
}

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn build_user(email: String, username: String) -> User {
    User {
        email,     // email: emailと書かなくて良い
        username , // username: usernameと書かなくていい
        active: true,
        sign_in_count: 1,
    }
}
email: test@example.com, user: some user
  • 構造体のインスタンスを返す関数が、上記のbuild_user
  • 関数の仮引数と構造体のフィールド名が同じ場合は、フィールド初期化の書き方を、例えばemail: emailからemailに省略可能

構造体更新記法

fn main() {
    // user1: 構造体のフィールド全てを定義
    let user1 = User {
        email: String::from("user1@example.com"),
        username: String::from("user1"),
        active: true,
        sign_in_count: 1,
    };

    // user2: user1の一部の値を使用してインスタンス生成
    let user2 = User {
        email: String::from("user2@example.com"),
        username: String::from("user2"),
        active: user1.active,
        sign_in_count: user1.sign_in_count,
    };

    // user3: 構造体更新記法による値の再利用
    let user3 = User {
        email: String::from("user3@example.com"),
        username: String::from("user3"),
        ..user1
    };

    println!("{:?}", user1);
    println!("{:?}", user2);
    println!("{:?}", user3);
}

#[derive(Debug)]
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}
User { username: "user1", email: "user1@example.com", sign_in_count: 1, active: true }
User { username: "user2", email: "user2@example.com", sign_in_count: 1, active: true }
User { username: "user3", email: "user3@example.com", sign_in_count: 1, active: true }
  • 他のインスタンスの値を利用して、新たなインスタンスを生成する方法として、user2user3の例を示す
  • user2は、user1の対応するフィールドにそれぞれ値を定義している。対して、user3構造体更新記法と呼ばれる方法で、フィールドへの設定を簡潔に書くことができる

タプル構造体

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(127, 127, 127);
    
    // Errors
    // let white = Color(255.0, 255, 255);
    // let red = Color(255, 0, 0, 0);

    println!("{} {} {}", black.0, black.1, black.2);
    println!("{} {} {}", origin.0, origin.1, origin.2);
}
  • 指定の型で構成されたタプルの構造体を定義可能
  • フィールドの型だけが存在しており、whiteredの例のように、定義と異なる値の設定は不可

ユニット様構造体

struct UnitStruct;

trait SayOkNg {
    fn say_ok(&self) -> String;
    fn say_ng(&self) -> String;
}

impl SayOkNg for UnitStruct {
    fn say_ok(&self) -> String {
        return String::from("Ok");
    }
    fn say_ng(&self) -> String {
        return String::from("Ng");
    }
}

fn main() {
    let unit_struct = UnitStruct;
    println!("{}", unit_struct.say_ok());
    println!("{}", unit_struct.say_ng());
}
Ok
Ng
  • フィールドのない構造体を定義するのがユニット様構造体。"ユニットさま"では無く"ユニットよう"
  • フィールドを持たず、トレイトだけで関数が実装できる。好きに振る舞いを決められるということ?

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

意味付けされた処理

fn main() {
    // 方法0. 変数2つ定義
    let width = 30;
    let height = 50;
    println!("方法0: {}", area(width, height));

    // 方法1. タプル
    let rect1 = (30, 50);
    println!("方法1: {}", area_tuple(rect1));

    // 方法2. 構造体
    let rect2 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("方法2: {}", area_struct(&rect2));
}

// 方法0
fn area(width: u32, height: u32) -> u32 {
    width * height
}

// 方法1
fn area_tuple(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

// 方法2
struct Rectangle {
    width: u32,
    height: u32,
}

fn area_struct(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

方法0: 1500
方法1: 1500
方法2: 1500
  • width, heightを渡して掛け算するコード
  • 方法0:愚直に変数を2つ定義して、それぞれを関数に渡す
  • 方法1:タプルを定義して、渡された値を関数の中でバラして計算。添え字の0と1が何であるかを意識しておく必要がある
  • 方法2:構造体を定義して計算しており、より意味付けがなされている
    • rect2では借用しているだけなので、処理の後にもrect2を使い続けることができる

トレイトの導入

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    // #[derive(Debug)] 無しではprintln!できない
    // println!("rect1 is {}", rect1);
    println!("rect1 is {:?}", rect1);
    println!("rect1 is {:#?}", rect1);
}
rect1 is Rectangle { width: 30, height: 50 }
rect1 is Rectangle {
    width: 30,
    height: 50,
}
  • 構造体をそのままprintln!しようとすると、Rectangle cannot be formatted with the default formatter; try using :? instead if you are using a format string と怒られる
  • println!の標準はDisplayという形で整形されて出力される。しかし、構造体だと「どんな形で出力すれば?カンマは必要?」と言ったように曖昧性が含まれるため、構造体にはDisplayが提供されていない
  • {}:?と指定すると、Debugという出力整形を行わせる指示になる。こちらは、構造体を出力できるためエラーが発生しない
    • ただし、構造体定義の直前に#[derive(Debug)]の注釈が必要
  • {:#?}と指定すると、1行で出力されていたものが、改行されて出力される

5.3章:メソッド記法

メソッドの定義

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn area_mut(&mut self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("{}", rect1.area());

    let mut rect1_mut = Rectangle {
        width: 30,
        height: 50,
    };
    println!("{}", rect1_mut.area_mut());
}
1500
1500
  • Rustはクラスをサポートしていないが、構造体はメソッドを持つことができ、implを使う
    • この型のインスタンスで実行可能なことをimplブロックに纏められていることは、メソッドのメリットの1つ
    • selfは自身のオブジェクトを示し、自身が持つwidth, heightを掛け算している
  • &selfだけでは無く、&mut selfと書くこともできるが、インスタンスがその後の処理で使われる想定であれば、rect1_mutのように可変参照を取ると良い
    • メソッド (impl) を使う時は、メソッドが読み込み専用 (&self)、書き込みを行う (&mut self) 、所有権を奪う (self) を考え使う
    • 上記のコードだけに限るなら、再利用性は不要なので、&selfで良い

引数の多いメソッド

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
Can rect1 hold rect2? true
Can rect1 hold rect3? false
  • &rect2の部分では不変借用を渡しているが、これは書き込みが必要なく、所有権をmainに残したいため道理に適っている

関連関数

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

fn main() {
    let sq = Rectangle::square(3);
    println!("area: {}", sq.area());
}
area: 9
  • implでは、selfを引数に取らない関数を定義できる。関数であり、メソッドでは無い
    • Struct::Methodの形で使用する
  • このパターンは、構造体の新しいインスタンスを作るためのコンストラクタとして利用されることが多い
  • メソッドと関連関数は、selfを引数に持つかが異なる点である

複数のimplブロック

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("area: {}", rect1.area());

    let rect2 = Rectangle { width: 10, height: 40 };
    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}
area: 1500
Can rect1 hold rect2? true
  • 構造体は、複数のimplブロックを定義可能
  • 上記は分ける意味は無いが、有用になるケースは10章で記載があるとのこと
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?