Rust の文法: 構造体 (struct) とタプル構造体 (tuple struct)

構造体の基本

Rust の構造体の定義とインスタンス化は直感的です。 構造体を定義するときは struct 構造体名 { ... }、定義済みの構造体をインスタンス化するときは 構造体名 { ... } という構文を使用します。 先頭に struct というキーワードが付くかどうかの違いしかないので、どちらの処理を行っているのか気を付けて読み取ってください。

// 構造体の定義(フィールド名と型を指定していく)
struct User {
    name: String,
    email: String,
    active: bool,
    sign_in_count: u64,
}

// インスタンス化(フィ―ルド名と初期値を指定していく)
let user = User {
    email: String::from("maku@example.com"),
    name: String::from("maku"),
    active: true,
    sign_in_count: 1,
};

// フィールドの参照にはドットを使う
println!("{}", user.name);

構造体のフィールドとして文字列を持ちたいときは、&str(文字列スライス)ではなく String 型を使うことで、構造体インスタンス自身が文字列の所有者になることができます(参考: 所有権と借用)。

構造体インスタンスの参照(借用)経由でフィールドにアクセスする場合もドットが使えます。

let user_ref = &user;  // 借用 (borrow) して参照 (&User) を作成
println!("{}", user_ref.name);  // 参照のフィールドもドットでアクセスする

可変な構造体 (mutable struct)

インスタンス生成時に let の代わりに let mut を使うと、可変 (mutable) なインスタンスとなり、すべてのフィールドが再代入可能になります(一部のフィールドだけを再代入可能にすることはできません)。

let mut user = User {
    // ...
};

// 各フィールドに別の値を代入可能
user.name = String::from("puni");
user.active = false;

次のように、構造体インスタンスを丸ごと入れ替えることもできます。

user = User {
    email: String::from("puni@example.com"),
    name: String::from("puni"),
    active: false,
    sign_in_count: 777,
};

フィールド初期化の省略記法

フィールド初期化時に、フィールド名と初期値とする変数名が同じ場合、フィールド名だけを記述する省略記法を使えます。

fn build_user(email: String, name: String) -> User {
    User {
        email,  // email: email と書くのと同じ
        name,   // name: name と書くのと同じ
        active: true,
        sign_in_count: 1,
    }
}

このような省略記法は TypeScript (JavaScript) でも採用されています。

部分的に異なるインスタンスを生成する

既存の構造体インスタンスがあるときに、一部のフィールドの値だけが異なる別のインスタンスを生成したいときは、次のように .. を使って残りのフィールドをコピーします。

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

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

..user1 の後ろにはカンマ (,) を付けないことに注意してください。 この構文もスプレッド演算子として TypeScript (JavaScript) に採用されています。

タプル構造体 (tuple struct)

構造体のフィールド名を省略した、タプル構造体 (tuple struct) を定義することができます。 通常のタプル に名前を付けたようなものと考えればよいです。 通常のタプルと同様に、各フィールドの値は、.0.1 のようなインデックスで参照します。

// Point という名前のタプル構造体を定義する
struct Point(i32, i32);

// 基本的な使い方
let p = Point(100, 200);
println!("{}, {}", p.0, p.1);  //=> 100, 200

通常のタプルと同様に、各フィールドの値を別々の変数に展開することができます。

// 各フィールドの値を別々の変数(x と y)に展開
let Point(x, y) = p;
println!("{x}, {y}");  //=> 100, 200

構造体のデバッグ出力

構造体を定義するときに #[derive(Debug)] という属性を付けておくと(あるいは Debug トレイト を自力で実装しておくと)、println! マクロで、{:?} により構造体の内容をデバッグ出力できるようになります(複数行で整形して出力したいときは {:?} の代わりに {:#?} を使用します)。

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 100, y: 200 };
println!("{:?}", p);
println!("{:#?}", p);
出力結果
Point { x: 100, y: 200 }
Point {
    x: 100,
    y: 200,
}

構造体のメソッドを実装する

構造体のメソッドは、impl ブロック内で定義します。 メソッドの第 1 引数は、構造体インスタンスの参照を示す &self になります(Python の文法と似ています)。 構造体の型は明らかなので、&self に型を指定する必要はありません。

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

// Rect のメソッドを実装する
impl Rect {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

let r = Rect { width: 5, height: 2 };
println!("{}", r.area()); //=> 10

構造体のフィールドを変更したい場合は、メソッドの第 1 引数を &mut self にします。 このメソッドを呼び出す場合は、構造体のインスタンスを生成するときに let mut を使わなければいけないことに注意してください。

impl Rect {
    // 自身のフィールドの内容を変更するメソッド
    fn double(&mut self) {
        self.width *= 2;
        self.height *= 2;
    }
}

let mut r = Rect { width: 5, height: 2 };
r.double();

関連関数 (associated function)

impl ブロック内で &self(や &mut self)を第 1 引数に取らない関数を定義すると、その構造体の 関連関数 (associated function) として扱われます。 他の言語では static メソッドと呼ばれているもので、呼び出し時にインスタンスを必要としません。 関連関数を呼び出すには 構造体名::関数名 という構文を使用します(C++ の static メソッドと同様です)。

関連関数としてファクトリ関数を定義する
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    // 関連関数 (静的メソッド)の定義
    fn origin() -> Point {
        Point { x: 0.0, y: 0.0 }
    }
}

// 関連関数を呼び出す
let p = Point::origin();

上の例では origin 関数の戻り値の型を Point としていますが、次のように Self というキーワードを使うことで、impl 対象の構造体の型が指定されたものとみなされます。

impl Point {
    fn origin() -> Self {
        Point { x: 0.0, y: 0.0 }
    }
}