構造体を定義し、インスタンス化する
構造体は、「タプル型」の節で議論したタプルと、どちらも関係する複数の値を抱えるという点で似ています。 タプル同様、構造体の一部を異なる型にできます。 一方タプルとは違って、構造体では各データ片には名前をつけるので、値の意味が明確になります。 これらの名前が付いていることで、構造体はタプルに比して、より柔軟になるわけです: データの順番に頼って、 インスタンスの値を指定したり、アクセスしたりする必要がないのです。
構造体の定義は、structキーワードを入れ、構造体全体に名前を付けます。構造体名は、
一つにグループ化されるデータ片の意義を表すものであるべきです。そして、波かっこ内に、
データ片の名前と型を定義し、これはフィールドと呼ばれます。例えば、リスト5-1では、
ユーザアカウントに関する情報を保持する構造体を示しています。
ファイル名: src/main.rs
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() {}
リスト5-1: User構造体定義
構造体を定義した後に使用するには、各フィールドに対して具体的な値を指定して構造体のインスタンスを生成します。 インスタンスは、構造体名を記述し、key: valueペアを含む波かっこを付け加えることで生成します。 ここで、キーはフィールド名、値はそのフィールドに格納したいデータになります。フィールドは、 構造体で宣言した通りの順番に指定する必要はありません。換言すると、構造体定義とは、 型に対する一般的な雛形のようなものであり、インスタンスは、その雛形を特定のデータで埋め、その型の値を生成するわけです。 例えば、リスト5-2で示されたように特定のユーザを宣言することができます。
ファイル名: src/main.rs
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; }
リスト5-2: User構造体のインスタンスを生成する
構造体から特定の値を得るには、ドット記法を使います。例えば、
このユーザのEメールアドレスにアクセスするには、user1.emailを使います。
インスタンスが可変であれば、ドット記法を使い特定のフィールドに代入することで値を変更できます。
リスト5-3では、可変なUserインスタンスのemailフィールド値を変更する方法を示しています。
ファイル名: src/main.rs
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let mut user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), 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が入ります。
ファイル名: src/main.rs
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn build_user(email: String, username: String) -> User { User { active: true, username: username, email: email, sign_in_count: 1, } } fn main() { let user1 = build_user( String::from("someone@example.com"), String::from("someusername123"), ); }
リスト5-4: Eメールとユーザ名を取り、Userインスタンスを返すbuild_user関数
構造体のフィールドと同じ名前を関数の引数にもつけることは筋が通っていますが、
emailとusernameというフィールド名と変数を繰り返さなきゃいけないのは、ちょっと面倒です。
構造体にもっとフィールドがあれば、名前を繰り返すことはさらに煩わしくなるでしょう。
幸運なことに、便利な省略記法があります!
フィールド初期化省略記法を使う
仮引数名と構造体のフィールド名がリスト5-4では、全く一緒なので、フィールド初期化省略記法を使ってbuild_userを書き換えても、
振る舞いは全く同じにしつつ、リスト5-5に示したようにusernameとemailを繰り返さなくてもよくなります。
ファイル名: src/main.rs
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn build_user(email: String, username: String) -> User { User { active: true, username, email, sign_in_count: 1, } } fn main() { let user1 = build_user( String::from("someone@example.com"), String::from("someusername123"), ); }
リスト5-5: usernameとemail引数が構造体のフィールドと同名なので、
フィールド初期化省略法を使用するbuild_user関数
ここで、emailというフィールドを持つUser構造体の新規インスタンスを生成しています。
emailフィールドをbuild_user関数のemail引数の値にセットしたいわけです。
emailフィールドとemail引数は同じ名前なので、email: emailと書くのではなく、
emailと書くだけで済むのです。
構造体更新記法で他のインスタンスからインスタンスを生成する
他のインスタンスからの値の多くの部分を含みつつ、一部を変更する形で新しいインスタンスを生成できるとしばしば有用です。 構造体更新記法でそうすることができます。
まず、リスト5-6では、更新記法なしで普通にuser2に新しいUserインスタンスを生成する方法を示しています。
emailには新しい値をセットしていますが、それ以外にはリスト5-2で生成したuser1の値を使用しています。
ファイル名: src/main.rs
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { // --snip-- let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; let user2 = User { active: user1.active, username: user1.username, email: String::from("another@example.com"), sign_in_count: user1.sign_in_count, }; }
リスト5-6: user1の一部の値を使用しつつ、新しいUserインスタンスを生成する
構造体更新記法を使用すると、リスト5-7に示したように、コード量を減らしつつ、同じ効果を達成できます。..という記法により、
明示的にセットされていない残りのフィールドが、与えられたインスタンスのフィールドと同じ値になるように指定します。
ファイル名: src/main.rs
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { // --snip-- 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"), ..user1 }; }
リスト5-7: 構造体更新記法を使用して、新しいUserインスタンス用の値に新しいemailをセットしつつ、
残りの値はuser1を使う
リスト5-7のコードも、emailについてはuser1とは異なる値、username、activeとsign_in_countフィールドについては、
user1と同じ値になるインスタンスをuser2に生成します。
..user1は、残りのフィールドについてはuser1の対応するフィールドから値を取る、ということを示すために最後に来る必要がありますが、
フィールドについては好きなだけ多く、構造体定義中のフィールドの順序とは無関係に好きな順で、値を指定してかまいません。
構造体更新記法は代入と同様に=を使います; これは、「ムーブによる変数とデータの相互作用」の節で見たのと同じように、
データをムーブするからです。この例で言うと、user2を作成した後は、もうuser1をそっくりそのまま使うことはできません。
user1のusernameフィールド中のStringがuser2の中にムーブされてしまったからです。
もしuser2に、emailとusernameのために新しいString値を与えていたなら、つまり、
user1からはactiveとsign_in_countの値だけを使用していたなら、user2を作成した後もuser1はまだ有効だったでしょう。
activeとsign_in_countはどちらもCopyトレイトを実装した型なので、「スタックのみのデータ: コピー」節で議論した振る舞いが適用されるからです。
異なる型を生成する名前付きフィールドのないタプル構造体を使用する
Rustは、構造体名により追加の意味を含むものの、フィールドに紐づけられた名前がなく、むしろフィールドの型だけのタプル構造体と呼ばれる、 タプルに似た構造体もサポートしています。タプル構造体は、構造体名が提供する追加の意味は含むものの、 フィールドに紐付けられた名前はありません; むしろ、フィールドの型だけが存在します。タプル構造体は、タプル全体に名前をつけ、 そのタプルを他のタプルとは異なる型にしたいが、普通の構造体のように各フィールド名を与えるのは、 冗長、または余計という場合に有用です。
タプル構造体を定義するには、structキーワードの後に構造体名、さらにタプルに含まれる型を続けてください。
例えば、ここでは、ColorとPointという2種類のタプル構造体の定義して使用します:
ファイル名: src/main.rs
struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0); }
blackとoriginの値は、違う型であることに注目してください。これらは、異なるタプル構造体のインスタンスだからですね。
定義された各構造体は、構造体内のフィールドが同じ型であっても、それ自身が独自の型になります。
例えば、Color型を引数に取る関数は、Pointを引数に取ることはできません。たとえ、両者の型が、
3つのi32値からできていてもです。それ以外については、タプル構造体のインスタンスは、
分配して個々の部品にしたり、.と添え字を使用して個々の値にアクセスできるという点で、タプルと似ています。
フィールドのないユニット様構造体
また、一切フィールドのない構造体を定義することもできます!これらは、()、
「タプル型」の節で言及したユニット型と似たような振る舞いをすることから、
ユニット様構造体と呼ばれます。ユニット様構造体は、ある型にトレイトを実装するけれども、
型自体に保持させるデータは一切ない場面に有効になります。トレイトについては第10章で議論します。
以下は、AlwaysEqualという名前のユニット様構造体を宣言し、インスタンス化する例です:
ファイル名: src/main.rs
struct AlwaysEqual; fn main() { let subject = AlwaysEqual; }
AlwaysEqualを定義するためには、structキーワード、付けたい名前、そしてセミコロンを使います。
波括弧や丸括弧は不要です!
次に、同じようにして、subject変数にAlwaysEqualのインスタンスを得られます: 波括弧や丸括弧を付けずに、定義した名前を使います。
後でこの型の振る舞いを、おそらくはテスト目的で既知の結果を得るために、AlwaysEqualのすべてのインスタンスが常に他の任意の型と等価であるように実装することを想像してください。
この挙動を実装するためにデータはまったく必要ないですね!
トレイトを定義して、ユニット様構造体も含めた任意の型にそれを実装する方法については10章で触れます。
構造体データの所有権
リスト5-1の
User構造体定義において、&str文字列スライス型ではなく、所有権のあるString型を使用しました。 これは意図的な選択です。というのも、この構造体の各インスタンスには自身の全データを所有してもらう必要があり、 このデータは、構造体全体が有効な間はずっと有効である必要があるのです。構造体に、他の何かに所有されたデータへの参照を保持させることもできますが、 そうするにはライフタイムという第10章で議論するRustの機能を使用しなければなりません。 ライフタイムのおかげで構造体に参照されたデータが、構造体自体が有効な間、ずっと有効であることを保証してくれるのです。 次のように、ライフタイムを指定せずに構造体に参照を保持させようとしたとしましょう。これは動きません:
ファイル名: src/main.rs
struct User { active: bool, username: &str, email: &str, sign_in_count: u64, } fn main() { let user1 = User { active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, }; }コンパイラは、ライフタイム指定子が必要だと怒るでしょう:
$ cargo run Compiling structs v0.1.0 (file:///projects/structs) error[E0106]: missing lifetime specifier (エラー: ライフタイム指定子がありません) --> src/main.rs:3:15 | 3 | username: &str, | ^ expected named lifetime parameter | (ライフタイム引数を予期しました) | help: consider introducing a named lifetime parameter | 1 ~ struct User<'a> { 2 | active: bool, 3 ~ username: &'a str, | error[E0106]: missing lifetime specifier --> src/main.rs:4:12 | 4 | email: &str, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct User<'a> { 2 | active: bool, 3 | username: &str, 4 ~ email: &'a str, | For more information about this error, try `rustc --explain E0106`. error: could not compile `structs` (bin "structs") due to 2 previous errors第10章で、これらのエラーを解消して構造体に参照を保持する方法について議論しますが、 当面、今回のようなエラーは、
&strのような参照の代わりに、Stringのような所有された型を使うことで修正します。