1. 概要
今回はRustの構造体について学んでいきたいと思います。
2. 構造体
構造体は、異なる型の複数の要素を1つにまとめたものです。それぞれの要素をフィールドを呼び、「struct」を使用して、フィールドには「名前」と「型」を指定します。
基本的な構造体の定義方法は、次のように書きます。
// CarSpec構造体の定義
struct CarSpec {
model: i32,
cc: i32,
color: i32,
}
fn main() {
let car1 = CarSpec {
model: 3001,
cc: 1500,
color: 0xFF0000,
};
let car2 = CarSpec {
model: 3002,
cc: 1200,
color: 0x0000FF,
};
println!(
"car1 model:{}, cc:{}, color:{:06x}",
car1.model, car1.cc, car1.color
);
println!(
"car2 model:{}, cc:{}, color:{:06x}",
car2.model, car2.cc, car2.color
);
}
結果
Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 2.97s
Running `target/debug/hello_rust`
car1 model:3001, cc:1500, color:ff0000
car2 model:3002, cc:1200, color:0000ff
この定義方法は他の言語でも良くある書き方です。
構造体の初期化の方法は他にもあります。
// CarSpec構造体の定義
struct CarSpec {
model: i32,
cc: i32,
color: i32,
}
fn main() {
let model = 3001;
let cc = 1500;
let color = 0xFF0000;
let car = CarSpec { model, cc, color };
println!(
"car model:{}, cc:{}, color:{:06x}",
car.model, car.cc, car.color
);
}
結果
Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/hello_rust`
car model:3001, cc:1500, color:ff0000
構造体のフィールド名と同じ変数名をもつ変数であれば、フィールド名を省略して書くこともできます。これは「フィールド初期化法」と言います。
構造体もデフォルトは不変です。可変にしたい場合は、次のように定義します。
// CarSpec構造体の定義
struct CarSpec {
model: i32,
cc: i32,
color: i32,
}
fn main() {
let mut car = CarSpec {
model: 3001,
cc: 1500,
color: 0xFF0000,
};
println!(
"car model:{}, cc:{}, color:{:06x}",
car.model, car.cc, car.color
);
// 色を変更
car.color = 0x0000FF;
println!(
"car model:{}, cc:{}, color:{:06x}",
car.model, car.cc, car.color
);
}
結果
Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.65s
Running `target/debug/hello_rust`
car model:3001, cc:1500, color:ff0000
car model:3001, cc:1500, color:0000ff
次は、すでに作成されている構造体オブジェクトを利用して、新しい構造体オブジェクトを作成する際に利用できる便利な構造体の更新記法をみてみましょう。
// User構造体の定義
struct User {
name: String,
age: i32,
}
fn main() {
let user1 = User {
name: String::from("taro"),
age: 18,
};
let user2 = User {
name: String::from("jiro"),
..user1
};
println!("user1 name={}, age={}", user1.name, user1.age);
println!("user2 name={}, age={}", user2.name, user2.age);
}
結果
Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 2.76s
Running `target/debug/hello_rust`
user1 name=taro, age=18
user2 name=jiro, age=18
注目してほしいのは、「..user1」の部分です。構造体の初期化の時に、明示的に指定していないフィールドは「..構造体オブジェクト」のように記述すると、「..」の後の構造体オブジェクトのフィールドをコピーしてくれます。これを「構造体更新記法」と言います。
注意すべき点は、プリミティブ型などのコピートレイトが実装されているものは値がコピーされますが、String型などは所有権の移動が発生するので、所有権を移動したくない時は、借用するなど工夫が必要です。
3. タプル構造体
タプル構造体とは、指定した要素で構成されたタプルの事です。タプル構造体にはフィールドはありません。タプル構造体は、同じ要素構成であっても違うものとして区別されます。タプルと同様に「タプル.0」と要素のインデックスを指定するなどして、値を取得できます。
// Body構造体を定義
#[derive(Debug)]
struct Body(f64, f64);
impl Body {
// BMI値を計算するメソッド
fn clac_bmi(&self) -> f64 {
let h = self.0 / 100.0;
self.1 / h.powf(2.0)
}
// 乖離率を計算するメソッド
fn clac_per(&self) -> f64 {
self.clac_bmi() / 22.0 * 100.0
}
}
fn main() {
// Body構造体のインスタンス生成
let taro = Body(189.8, 90.8);
// 入力値
println!("入力値={:?}", taro);
// BMI値を出力
println!("BMI={}", taro.clac_bmi());
// 乖離率を出力
println!("乖離率={}", taro.clac_per());
}
結果
Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/hello_rust`
入力値=Body(189.8, 90.8)
BMI=25.205390622484316
乖離率=114.5699573749287
4. ユニット構造体
ユニット構造体とは、フィールドを持たない構造体です。トレイトなどを扱うときに使用されるそうです。
struct Body;
5. メソッド
メソッドと関数と似ていますが、違うものです。関数との違いは、構造体を関連していて、第1引数に必ず「self」を持っていることです。「self」を使用することで、そのメソッドを呼び出したオブジェクトを操作することが出来ます。
メソッドは、「impl」ブロックの中で関数と同じように「fn」を使用して定義します。
// Body構造体を定義
#[derive(Debug)]
struct Body {
height: f64,
weight: f64,
}
impl Body {
// BMI値を計算するメソッド
fn clac_bmi(&self) -> f64 {
let h = self.height / 100.0;
self.weight / h.powf(2.0)
}
// 乖離率を計算するメソッド
fn clac_per(&self) -> f64 {
self.clac_bmi() / 22.0 * 100.0
}
}
fn main() {
// Body構造体のインスタンス生成
let taro = Body {
height: 175.0,
weight: 86.6,
};
// 入力値
println!("入力値={:?}", taro);
// BMI値を出力
println!("BMI={}", taro.clac_bmi());
// 乖離率を出力
println!("乖離率={}", taro.clac_per());
}
結果
Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.20s
Running `target/debug/hello_rust`
入力値=Body { height: 175.0, weight: 86.6 }
BMI=28.27755102040816
乖離率=128.5343228200371
関連関数
「impl」ブロックの中で、関数を定義することができます。関連関数は、第1引数に「self」を持ちません。このように構造体と関連するしているにも関わらず、「self」を引数に持たない関数を「関連関数」と言います。
関連関数は主に、対象に構造体のコンストラクタとして使われていて、「new()」という名前で使われます。関連関数を呼ぶ出すときは「構造体::関連関数」のように書きます。
// Body構造体を定義
#[derive(Debug)]
struct Body {
height: f64,
weight: f64,
}
impl Body {
// インスタンス生成する関連関数
fn new(height: f64, weight: f64) -> Body {
Body { height, weight }
}
// BMI値を計算するメソッド
fn clac_bmi(&self) -> f64 {
let h = self.height / 100.0;
self.weight / h.powf(2.0)
}
// 乖離率を計算するメソッド
fn clac_per(&self) -> f64 {
self.clac_bmi() / 22.0 * 100.0
}
}
fn main() {
// Body構造体のインスタンス生成
let taro = Body::new(168.8, 61.8);
// 入力値
println!("入力値={:?}", taro);
// BMI値を出力
println!("BMI={}", taro.clac_bmi());
// 乖離率を出力
println!("乖離率={}", taro.clac_per());
}
結果
Compiling hello_rust v0.1.0 (<作業ディレクトリ>/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/hello_rust`
入力値=Body { height: 168.8, weight: 61.8 }
BMI=21.68920284809415
乖離率=98.58728567315524
6. まとめ
今回は、構造体についてやっていきました。構造体があればデータの扱いがしやすくなりましたね。ユニット構造体とかはトレイトの時に掘り下げていきたいと思ってます。
投稿者プロフィール
最新の投稿
- 【Remix】2024年3月23日【Remix】 環境構築手順
- 【Rust】2022年11月27日【Rust】Unsafe Rust
- 【Rust】2022年11月27日【Rust】列挙型
- 【Rust】2022年10月22日【Rust】構造体