構造体を定義し、インスタンス化する
構造体は第3章で議論したタプルと似ています。タプル同様、構造体の一部を異なる型にできます。 一方タプルとは違って、各データ片には名前をつけるので、値の意味が明確になります。 この名前のおかげで、構造体はタプルに比して、より柔軟になるわけです: データの順番に頼って、 インスタンスの値を指定したり、アクセスしたりする必要がないのです。
構造体の定義は、struct
キーワードを入れ、構造体全体に名前を付けます。構造体名は、
一つにグループ化されるデータ片の意義を表すものであるべきです。そして、波かっこ内に、
データ片の名前と型を定義し、これはフィールドと呼ばれます。例えば、リスト5-1では、
ユーザアカウントに関する情報を保持する構造体を示しています。
#![allow(unused)] fn main() { struct User { username: String, email: String, sign_in_count: u64, active: bool, } }
構造体を定義した後に使用するには、各フィールドに対して具体的な値を指定して構造体のインスタンスを生成します。
インスタンスは、構造体名を記述し、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, }; }
構造体から特定の値を得るには、ドット記法が使えます。このユーザの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"); }
インスタンス全体が可変でなければならないことに注意してください; 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, } } }
構造体のフィールドと同じ名前を関数の引数にもつけることは筋が通っていますが、
email
とusername
というフィールド名と変数を繰り返さなきゃいけないのは、ちょっと面倒です。
構造体にもっとフィールドがあれば、名前を繰り返すことはさらに煩わしくなるでしょう。
幸運なことに、便利な省略記法があります!
フィールドと変数が同名の時にフィールド初期化省略記法を使う
仮引数名と構造体のフィールド名がリスト5-4では、全く一緒なので、フィールド初期化省略記法を使ってbuild_user
を書き換えても、
振る舞いは全く同じにしつつ、リスト5-5に示したようにemail
とusername
を繰り返さなくてもよくなります。
#![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, } } }
ここで、email
というフィールドを持つUser
構造体の新規インスタンスを生成しています。
email
フィールドをbuild_user
関数のemail
引数の値にセットしたいわけです。
email
フィールドとemail
引数は同じ名前なので、email: email
と書くのではなく、
email
と書くだけで済むのです。
構造体更新記法で他のインスタンスからインスタンスを生成する
多くは前のインスタンスの値を使用しつつ、変更する箇所もある形で新しいインスタンスを生成できるとしばしば有用です。 構造体更新記法でそうすることができます。
まず、リスト5-6では、更新記法なしでuser2
に新しいUser
インスタンスを生成する方法を示しています。
email
とusername
には新しい値をセットしていますが、それ以外にはリスト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-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のコードも、email
とusername
については異なる値、active
とsign_in_count
フィールドについては、
user1
と同じ値になるインスタンスをuser2
に生成します。
異なる型を生成する名前付きフィールドのないタプル構造体を使用する
構造体名により追加の意味を含むものの、フィールドに紐づけられた名前がなく、むしろフィールドの型だけのタプル構造体と呼ばれる、 タプルに似た構造体を定義することもできます。タプル構造体は、構造体名が提供する追加の意味は含むものの、 フィールドに紐付けられた名前はありません; むしろ、フィールドの型だけが存在します。タプル構造体は、タプル全体に名前をつけ、 そのタプルを他のタプルとは異なる型にしたい場合に有用ですが、普通の構造体のように各フィールド名を与えるのは、 冗長、または余計になるでしょう。
タプル構造体を定義するには、struct
キーワードの後に構造体名、さらにタプルに含まれる型を続けます。
例えば、こちらは、Color
とPoint
という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); }
black
とorigin
の値は、違う型であることに注目してください。これらは、異なるタプル構造体のインスタンスだからですね。
定義された各構造体は、構造体内のフィールドが同じ型であっても、それ自身が独自の型になります。
例えば、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
のような所有された型を使うことで修正します。