Chapter 07

構造体

📌 構造体

構造体 はデータ型の要素を集めたものです.1つ1つの要素を フィールド と呼びます.構造体の定義は struct を使い,フィールドは名前と型を指定します.

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

構造体のオブジェクトを作成する場合は,各フィールドを key:value という形で束縛します.

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

ここでは「 オブジェクト 」と「 インスタンス 」について説明します.一般的に構造体や列挙型など(オブジェクト指向でのクラス)の実体は「インスタンス」と呼ばれています.しかし,本書ではそれらを「オブジェクト」に統一します.そして,「インスタンス」は,関数型プログラミング言語 Haskell に従って, データ型 を表します.例えば,コピートレイトを実装した構造体 Hoge があるとします.このとき, Hoge はコピートレイトの インスタンスデータ型 )です.そして,トレイト境界でコピートレイトを指定した場合,コピートレイトのインスタンスである Hoge は制約を満たしていることになります.

可変のオブジェクトを作成するとすべてのフィールドが可変になります.オブジェクトのフィールドは . 演算子を使って指定します.

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

user1.email = String::from("anotheremail@example.com");

オブジェクト作成時に指定する変数名と構造体のフィールド名が一致している場合,フィールド名を省略することが出来ます.

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

あるオブジェクトのフィールドに束縛したものを使って,新しいオブジェクトを作成するときに便利な構文があります.オブジェクト作成時に,明示的にフィールドを指定しなかったものは .. の後に渡したオブジェクトのフィールドを束縛します.ただし,コピートレイトを実装している型なら複製され,そうでないなら所有権が移動することに注意が必要です.

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

📌 タプル構造体

指定した要素で構成されたタプルに名前をつけることが出来ます.このようなタプルを タプル構造体 といいます.この場合,フィールド名はありません.タプル構造体は同じ構成をしていても別の型として区別されます.タプルは .0 というように要素のインデックスを指定するか,要素の分解を使います.

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

fn main() {
    let black = Color(0, 0, 0);
    let Point (x,y,z) = Point(0, 0, 0);
    
    println!("{} {} {}", black.0, black.1, black.2);
    println!("{} {} {}", x, y, z);
}

Rust は静的型付け言語です.この特性を利用して既存の型から新しい型を作成することで意図的な意味を付加させて,制約することが出来ます.また,既存の型を利用するので薄いラッパー型と考えることも出来ます.これは Newtype パターンと呼ばれるもので,タプル構造体を利用する例の1つです.
例えば, String 型から Password 型を作成し, {} で出力したときに伏せ字にする場合は次のようになります.

use std::fmt;

struct Password(String);

impl fmt::Display for Password {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0.chars().map(|_| '*').collect::<String>())
    }
}

fn main() {
    let a = String::from("123456789");
    println!("{}", a); // 123456789
    
    let a = Password(String::from("123456789"));
    println!("{}", a); // *********
}

📌 ユニット構造体

() のことをユニットと言いました.このようなフィールドを何も持たない構造体のことを ユニット構造体 (Unit-like Structs)と言います.(日本語ドキュメントだとユニット様構造体と翻訳されていますが,ユニット構造体の方が言いやすいし意味も伝わるでしょう).ユニット構造体はフィールドを持たず,トレイトだけ実装するといった時に使われるようです.

📌 メソッド

メソッド は関数に似ていますが,構造体と関連していて, self を使うことで,そのメソッドを呼び出したオブジェクトを操作することが出来ます.メソッドの第一引数は必ず self になります.また,基本的に不変参照 (&self) か可変参照 (&mut self) になります.もちろん,可変参照のメソッドは,呼び出し元が可変の所有権を使って呼び出さなければなりません.メソッドは impl ブロックの中で,関数と同じく fn を使って定義します.

struct Rectangle {
    width: u32,
    height: u32,
}

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

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

    println!("The area of the rectangle is {} square pixels.", rect1.area());
}

impl ブロックは複数定義することが出来ます.トレイトごとに実装を分けたりすることが出来ます.

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
    }
}

メソッドの第一引数が参照ではなく self の場合があります.これは呼び出し元のオブジェクトの所有権をメソッドが受け取ります.つまり,このメソッドを呼び出したとき,呼び出し元のオブジェクトが束縛されていたら,それは解除され使用できなくなるということです.これはメソッド呼び出しによってオブジェクトが別のものに変換するといったときに使われるようです.例えば,後に出てくる OptionResultunwrap というメソッドは unwrap(self) です.

📌 関連関数

impl ブロックの中で関数を定義することが出来ます.それは self を引数に取りません.このような関数を 関連関数 と呼びます.これは構造体に関連しているにも関わらず,そのオブジェクトが無くても呼び出すことが出来ます.関連関数は主にその構造体のオブジェクトを生成する関数の定義に使い,そのような関連関数には new という名前が使われます.関連関数は構造体の名前に :: を使って呼び出します.

struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn new(x: f64, y: f64) -> Self { // Self は実装している型の型エイリアス
        Self { x, y }
    }
}

fn main() {
    let a = Point::new(3., 5.);

    print!("x={}, y={}", a.x, a.y);
}