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

構造体は第3章で議論したタプルと似ています。タプル同様、構造体の一部を異なる型にできます。 一方タプルとは違って、各データ片には名前をつけるので、値の意味が明確になります。 この名前のおかげで、構造体はタプルに比して、より柔軟になるわけです: データの順番に頼って、 インスタンスの値を指定したり、アクセスしたりする必要がないのです。

構造体の定義は、structキーワードを入れ、構造体全体に名前を付けます。構造体名は、 一つにグループ化されるデータ片の意義を表すものであるべきです。そして、波かっこ内に、 データ片の名前と型を定義し、これはフィールドと呼ばれます。例えば、リスト5-1では、 ユーザアカウントに関する情報を保持する構造体を示しています。


#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
}

リスト5-1: User構造体定義

構造体を定義した後に使用するには、各フィールドに対して具体的な値を指定して構造体のインスタンスを生成します。 インスタンスは、構造体名を記述し、key: valueペアを含む波かっこを付け加えることで生成します。 ここで、キーはフィールド名、値はそのフィールドに格納したいデータになります。フィールドは、 構造体で宣言した通りの順番に指定する必要はありません。換言すると、構造体定義とは、 型に対する一般的な雛形のようなものであり、インスタンスは、その雛形を特定のデータで埋め、その型の値を生成するわけです。 例えば、リスト5-2で示されたように特定のユーザを宣言することができます。


#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

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

リスト5-2: User構造体のインスタンスを生成する

構造体から特定の値を得るには、ドット記法が使えます。このユーザのEメールアドレスだけが欲しいなら、 この値を使いたかった場所全部でuser1.emailが使えます。インスタンスが可変であれば、 ドット記法を使い特定のフィールドに代入することで値を変更できます。リスト5-3では、 可変なUserインスタンスのemailフィールド値を変更する方法を示しています。


#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

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");
}

リスト5-3: あるUserインスタンスのemailフィールド値を変更する

インスタンス全体が可変でなければならないことに注意してください; Rustでは、一部のフィールドのみを可変にすることはできないのです。 また、あらゆる式同様、構造体の新規インスタンスを関数本体の最後の式として生成して、 そのインスタンスを返すことを暗示できます。

リスト5-4は、与えられたemailとusernameでUserインスタンスを生成するbuild_user関数を示しています。 activeフィールドにはtrue値が入り、sign_in_countには値1が入ります。


#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

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

リスト5-4: Eメールとユーザ名を取り、Userインスタンスを返すbuild_user関数

構造体のフィールドと同じ名前を関数の引数にもつけることは筋が通っていますが、 emailusernameというフィールド名と変数を繰り返さなきゃいけないのは、ちょっと面倒です。 構造体にもっとフィールドがあれば、名前を繰り返すことはさらに煩わしくなるでしょう。 幸運なことに、便利な省略記法があります!

フィールドと変数が同名の時にフィールド初期化省略記法を使う

仮引数名と構造体のフィールド名がリスト5-4では、全く一緒なので、フィールド初期化省略記法を使ってbuild_userを書き換えても、 振る舞いは全く同じにしつつ、リスト5-5に示したようにemailusernameを繰り返さなくてもよくなります。


#![allow(unused)]
fn main() {
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,
    }
}
}

リスト5-5: emailusername引数が構造体のフィールドと同名なので、 フィールド初期化省略法を使用するbuild_user関数

ここで、emailというフィールドを持つUser構造体の新規インスタンスを生成しています。 emailフィールドをbuild_user関数のemail引数の値にセットしたいわけです。 emailフィールドとemail引数は同じ名前なので、email: emailと書くのではなく、 emailと書くだけで済むのです。

構造体更新記法で他のインスタンスからインスタンスを生成する

多くは前のインスタンスの値を使用しつつ、変更する箇所もある形で新しいインスタンスを生成できるとしばしば有用です。 構造体更新記法でそうすることができます。

まず、リスト5-6では、更新記法なしでuser2に新しいUserインスタンスを生成する方法を示しています。 emailusernameには新しい値をセットしていますが、それ以外にはリスト5-2で生成したuser1の値を使用しています。


#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

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

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

リスト5-6: user1の一部の値を使用しつつ、新しいUserインスタンスを生成する

構造体更新記法を使用すると、リスト5-7に示したように、コード量を減らしつつ、同じ効果を達成できます。..という記法により、 明示的にセットされていない残りのフィールドが、与えられたインスタンスのフィールドと同じ値になるように指定します。


#![allow(unused)]
fn main() {
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

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

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

リスト5-7: 構造体更新記法を使用して、新しいUserインスタンス用の値に新しいemailusernameをセットしつつ、 残りの値は、user1変数のフィールド値を使う

リスト5-7のコードも、emailusernameについては異なる値、activesign_in_countフィールドについては、 user1と同じ値になるインスタンスをuser2に生成します。

異なる型を生成する名前付きフィールドのないタプル構造体を使用する

構造体名により追加の意味を含むものの、フィールドに紐づけられた名前がなく、むしろフィールドの型だけのタプル構造体と呼ばれる、 タプルに似た構造体を定義することもできます。タプル構造体は、構造体名が提供する追加の意味は含むものの、 フィールドに紐付けられた名前はありません; むしろ、フィールドの型だけが存在します。タプル構造体は、タプル全体に名前をつけ、 そのタプルを他のタプルとは異なる型にしたい場合に有用ですが、普通の構造体のように各フィールド名を与えるのは、 冗長、または余計になるでしょう。

タプル構造体を定義するには、structキーワードの後に構造体名、さらにタプルに含まれる型を続けます。 例えば、こちらは、ColorPointという2種類のタプル構造体の定義と使用法です:


#![allow(unused)]
fn main() {
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

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

blackoriginの値は、違う型であることに注目してください。これらは、異なるタプル構造体のインスタンスだからですね。 定義された各構造体は、構造体内のフィールドが同じ型であっても、それ自身が独自の型になります。 例えば、Color型を引数に取る関数は、Pointを引数に取ることはできません。たとえ、両者の型が、 3つのi32値からできていてもです。それ以外については、タプル構造体のインスタンスは、 タプルと同じように振る舞います: 分配して個々の部品にしたり、.と添え字を使用して個々の値にアクセスするなどです。

フィールドのないユニット(よう)構造体

また、一切フィールドのない構造体を定義することもできます!これらは、()、ユニット型と似たような振る舞いをすることから、 ユニット様構造体と呼ばれます。ユニット様構造体は、ある型にトレイトを実装するけれども、 型自体に保持させるデータは一切ない場面に有効になります。トレイトについては第10章で議論します。

構造体データの所有権

リスト5-1のUser構造体定義において、&str文字列スライス型ではなく、所有権のあるString型を使用しました。 これは意図的な選択です。というのも、この構造体のインスタンスには全データを所有してもらう必要があり、 このデータは、構造体全体が有効な間はずっと有効である必要があるのです。

構造体に、他の何かに所有されたデータへの参照を保持させることもできますが、 そうするにはライフタイムという第10章で議論するRustの機能を使用しなければなりません。 ライフタイムのおかげで構造体に参照されたデータが、構造体自体が有効な間、ずっと有効であることを保証してくれるのです。 ライフタイムを指定せずに構造体に参照を保持させようとしたとしましょう。以下の通りですが、これは動きません:

ファイル名: src/main.rs

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

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

コンパイラは、ライフタイム指定子が必要だと怒るでしょう:

error[E0106]: missing lifetime specifier
(エラー: ライフタイム指定子がありません)
 -->
  | 
2 |     username: &str,
  |               ^ expected lifetime parameter
                   (ライフタイム引数を予期しました)

error[E0106]: missing lifetime specifier
 -->
  | 
3 |     email: &str,
  |            ^ expected lifetime parameter

第10章で、これらのエラーを解消して構造体に参照を保持する方法について議論しますが、 当面、今回のようなエラーは、&strのような参照の代わりに、Stringのような所有された型を使うことで修正します。

関連キーワード:  email, フィールド, User, username, インスタンス, 定義, user, count, active, データ