Rustの構造体の使い方を解説します【フィールド、タプル、ユニット型】
- 作成日: 2022-11-25
- 更新日: 2023-12-24
- カテゴリ: Rust
Rustの構造体の使い方は?
Rustでは構造体(struct
)を使えます。
構造体はデータをまとめたい時に使うものです。
またimpl
キーワードなどを使えば構造体にメソッドを定義することもできます。
高度なプログラミングでは構造体を使って処理を書くのが一般的です。
Rustの構造体には
- フィールド型構造体
- タプル型構造体
- ユニット型構造体
の3つがあります。
この記事ではRustの構造体をまとめて解説します。
一般的なフィールド型構造体の定義
まずもっとも一般的な構造体がこのフィールド型構造体です。
構造体には変数(メンバまたはフィールド)を定義できます。
フィールド型構造体はフィールドをずらっと並べている構造体です。
// nameとageをフィールドに持つ構造体Bird。
// nameは静的文字列、ageは符号なし整数。
struct Bird {
name: &'static str,
age: usize,
}
// 構造体をオブジェクトにする場合はブレースを使う。
let bird = Bird {
name: "Taro", // フィールドを指定して値を初期化する。
age: 20,
};
println!("{} {}", bird.name, bird.age); // Taro 20
構造体の定義ではstruct
の後に構造体名を書きます。
それからブレース({}
)を書いてその中に「フィールド名: 型名,
」という感じでフィールドを定義します。
構造体をオブジェクトにしたい場合は構造体名にブレースを付けてメンバに実際の値を指定します。
構造体オブジェクトにドット(.
)を付けるとフィールドにアクセスできます。
タプル型構造体の定義
次にタプルのように使える構造体がタプル型構造体です。
// i32の値を2つ持つことができるタプル構造体。
struct Point(i32, i32);
let point = Point(10, 20);
println!("{} {}", point.0, point.1); // 10 20
// タプルを分解
let Point (x, y) = point;
println!("{} {}", x, y); // 10 20
println!("{} {}", point.0, point.1); // 10 20
タプル型構造体にはフィールド名が必要ありません。
ですので型を指定するだけでOKです。
またタプル型構造体のオブジェクトのフィールドにアクセスしたい場合は「point.0
」や「point.1
」のように添え字でアクセスします。
またタプル型構造体の変数は
let Point (x, y) = point;
のようにフィールドを個別の変数に展開することもできます。
これはフィールドの型によってはムーブセマンティクスが働くこともあります。
ユニット型構造体の定義
フィールドを何ももたない、空っぽの構造体。
それがユニット型構造体です。
// フィールドもタプルも持たない空っぽの構造体。
struct MyUnit;
// でもトレイトの実装はできる。
trait Text {
fn get_text(&self) -> String;
}
impl Text for MyUnit {
fn get_text(&self) -> String {
return String::from("content");
}
}
// 空っぽの構造体からメソッドを呼び出すことができる。
let my_unit = MyUnit;
println!("{}", my_unit.get_text()); // content
ユニット型構造体は使い道が一見するとなさそうですが、ユニット型構造体にはトレイトで関数を実装できるという特徴があります。
つまりジェネリックプログラミングなどで構造体が必要になった場合にユニット構造体を使えばトレイトの実装の振る舞いをする空っぽの構造体を作れるということです。
トレイトの実装が必要になったときにいちいち使わないフィールド付きの構造体を定義しなければいけないのは無駄ですよね。
ユニット構造体を使えばそういった無駄を減らせます。
構造体メンバのムーブセマンティクスについて
構造体のムーブセマンティクスはどうなってるんでしょうか?
ちょっとフィールド付き構造体で試してみましょう。
struct Pig {
name: String,
age: i32,
}
let pig1 = Pig {
name: String::from("Tonchan"),
age: 20,
};
let pig2 = pig1; // move!
println!("{} {}", pig2.name, pig2.age); // Tonchan 20
// println!("{}", pig1.age); // error! value borrowed here after move
// println!("{}", pig1.name); // error! value borrowed here after move
結果を見るとしっかりpig1
がpig2
にムーブされてます。
このように構造体の代入でもムーブは働きます。
String
などはコピーを作りたい場合はclone()
メソッドなどを使います。
自分の構造体でコピーを働かせたい場合はclone()
メソッドを作るかCopy
トレイトを実装するといいでしょう。
タプル型構造体のムーブセマンティクスについて
もちろんタプル型構造体もムーブセマンティクスが働きます。
struct Name(String, String);
let name = Name(String::from("Good"), String::from("Morning"));
println!("{} {}", name.0, name.1); // Good Morning
let Name(first, last) = name; // move!
println!("{} {}", first, last);
// println!("{} {}", name.0, name.1); // error! value borrowed here after move
Rustはこのようにデフォルトでムーブが働きますので他の言語からやってきた人は注意が必要です。
ムーブというのは所有権の移動のことで、つまりメモリを誰が開放するか? という責任の所在です。
Rustではこのムーブセマンティクスを活用することで速度のあるプログラムを作れるように工夫しています。
構造体にメソッドを定義する
構造体にメソッドを定義します。
メソッドを定義する場合はimpl
キーワードを使います。
struct Friend {
name: String,
age: i32,
}
// Friend構造体にメソッドを実装する。
impl Friend {
// コンストラクタ。
// 静的関数。「::」を付けて呼び出せる。
fn new() -> Self { // 「Self」は「Friend」を表す。
Friend {
name: String::from("Taro"),
age: 20,
}
}
// メソッド。「.」を付けて呼び出せる。
fn get_name(&self) -> &String {
&self.name
}
}
let friend = Friend::new(); // 構造体をコンストラクト。
println!("{}", friend.get_name()); // メソッドを使う。
慣例的に構造体のコンストラクタは「new
」と付けることが多いみたいです。
コンストラクトの引数には「&self
」を書かないでおきます。
こうするとFriend::new()
という風に関数を静的に呼び出すことができます。
String::new()
とかと同じですね。
メソッドには引数に&self
を書きます。
こうするとその関数がメソッドと見なされてfriend.get_name()
のようにそのメソッドを呼び出せるようになります。
おわりに
今回はRustの構造体(struct
)について解説しました。
構造体をマスターして快適なRustライフをお送りください。
🦝 < 構造体でデータを定義!
🐭 < ムーブしちゃう!