Rust 構造体

Rustの構造体(Struct)とタプル(Tuple)はどちらも、型が必ずしも同じではない複数のデータをまとめて1つの全体にすることができます。しかし、構造体の各メンバーとそれ自体には名前があり、そのメンバーにアクセスするときにインデックスを覚えておく必要がありません。タプルは定義されていない多値の受け渡しによく使われ、構造体は一般的なデータ構造を定義するために使われます。構造体の各メンバーは「フィールド」と呼ばれます。

構造体の定義

これは構造体の定義です:

struct Site {
    domain: String,
    name: String,
    nation: String,
    found: u32
}

注意:C/C++をよく使うなら、Rustではstruct文は定義にのみ使用され、インスタンスを宣言できないことを覚えておいてください。末尾には;記号は不要で、各フィールドの定義後には,区切り記号を使用します。

構造体インスタンス

Rustは多くの点でJavaScriptの影響を受けており、構造体をインスタンス化する際にJSONオブジェクトのkey: value構文を使用して定義します:

例

let サンプル = Site {
    domain: String::from("www.example.com"),
    name: String::from("サンプル"),
    nation: String::from("China"),
    found: 2013
};

JSONオブジェクトを知らなくても、気にせずに形式を覚えれば大丈夫です:

構造体クラス名 {
    フィールド名 : フィールド値,
    ...
}

これの利点は、プログラムがより直感的になるだけでなく、定義された順序に従ってメンバーの値を入力する必要がないことです。

インスタンス化しようとしている構造体に、既存の変数名と同じフィールド名がある場合は、簡潔に記述できます:

例


let domain = String::from("www.example.com");
let name = String::from("サンプル");
let サンプル = Site {
    domain,  // domain : domain と同じ
    name,    // name : name と同じ
    nation: String::from("China"),
    traffic: 2013
};

このような場合があります:新しい構造体インスタンスを作成したいが、そのほとんどのプロパティを既存の構造体と同じに設定し、1つか2つのフィールドの値だけを変更したい場合は、構造体更新構文を使用できます:

let site = Site {
    domain: String::from("www.example.com"),
    name: String::from("サンプル"),
    ..サンプル
};

注意:..サンプル後ろにカンマを付けることはできません。この構文では、別の構造体インスタンスをそのままコピーすることはできません。つまり、少なくとも1つのフィールドの値を再設定してからでないと、他のインスタンスの値を参照できないということです。

タプル構造体

構造体を定義して使用する、より簡単な方法があります:タプル構造体。

タプル構造体は、形式がタプルである構造体です。

タプルとの違いは、名前と固定された型形式があることです。その存在意義は、型を定義する必要がある(頻繁に使用する)が、複雑にしたくない単純なデータを処理するためです:

struct Color(u8, u8, u8);
struct Point(f64, f64);

let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);

「色」と「点座標」はよく使われる2つのデータ型ですが、インスタンス化するときに波括弧を書いて2つの名前を書くのは、可読性のために利便性を犠牲にすることになります。Rustはこの問題を残しません。タプル構造体オブジェクトの使用法はタプルと同じで、`.`と添え字でアクセスします:

例

fn main() {
    struct Color(u8, u8, u8);
    struct Point(f64, f64);

    let black = Color(0, 0, 0);
    let origin = Point(0.0, 0.0);

    println!("black = ({}, {}, {})", black.0, black.1, black.2);
    println!("origin = ({}, {})", origin.0, origin.1);
}

実行結果:

black = (0, 0, 0)
origin = (0, 0)

構造体の所有権

構造体はフィールド値の所有権を保持しなければなりません。構造体が無効になるとすべてのフィールドが解放されるからです。

これが、この章の例で`&str`ではなく`String`型を使用している理由です。

しかし、これは構造体に参照型フィールドを定義できないという意味ではなく、「ライフタイム」メカニズムによって実現できます。

しかし、今は「ライフタイム」の概念を説明するのは難しいため、後の章で説明します。

構造体の出力

デバッグ中に、構造体インスタンスを完全に表示することは非常に便利です。しかし、手動でフォーマットを記述するのは非常に不便です。そのためRustには、構造体全体を簡単に出力する方法が用意されています:

例

#[derive(Debug)]

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

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

    println!("rect1 is {:?}", rect1);
}

最初の行のように、デバッグライブラリを必ずインポートしてください#[derive(Debug)]。その後、`println`マクロと`print`マクロで{:?}プレースホルダーを使用して構造体全体を出力できます:

rect1 is Rectangle { width: 30, height: 50 }

属性が多い場合は、別のプレースホルダーを使用できます{:#?} 。

出力結果:

rect1 is Rectangle {
    width: 30,
    height: 50
}

構造体メソッド

メソッドは関数に似ていますが、構造体インスタンスを操作するために使用される点が異なります。

オブジェクト指向言語を学んだことがあれば、関数は通常クラス定義内に置かれ、関数内で`this`を使って操作対象のインスタンスを表すことをよく知っているでしょう。

Rust言語はオブジェクト指向ではありませんが、これは所有権メカニズムの革新からわかります。しかし、オブジェクト指向の貴重な思想はRustで実装できます。

構造体メソッドの最初のパラメータは`&self`でなければならず、型を宣言する必要はありません。`self`はスタイルではなくキーワードだからです。

長方形の面積を計算します:

例

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!("rect1's area is {}", rect1.area());
}

出力結果:

rect1's area is 1500

構造体メソッドを呼び出す際には`self`を記述する必要がないことに注意してください。これは使いやすさを考慮したものです。

複数のパラメータを持つ例:

例

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

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

    fn wider(&self, rect: &Rectangle) -> bool {
        self.width > rect.width
    }
}

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

    println!("{}", rect1.wider(&rect2));
}

実行結果:

false

このプログラムは、`rect1`が`rect2`より幅が広いかどうかを計算します。


構造体関連関数

「構造体メソッド」が「構造体関数」と呼ばれないのは、「関数」という名前が次のような関数に取ってあるからです:`impl`ブロック内にありながら`&self`パラメータを持たない関数です。

この関数はインスタンスに依存しませんが、使用するにはどの`impl`ブロックにあるかを宣言する必要があります。

これまで使用してきたString::from関数は「関連関数」です。

例

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

impl Rectangle {
    fn create(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let rect = Rectangle::create(30, 50);
    println!("{:?}", rect);
}

実行結果:

Rectangle { width: 30, height: 50 }

ヒント:構造体の`impl`ブロックは複数回記述でき、その効果はそれらの内容を連結したものと同じです!

ユニット構造体

構造体は、メンバーを一切持たずに、象徴としてのみ存在することができます:

struct UnitStruct;

このような本体を持たない構造体をユニット構造体(Unit Struct)と呼びます。

その他の拡張