【Rust】がばがばRust独学 - 5. Structs
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回は、Structsについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
Structs(構造体)
構造体はタプルと似ていますが、各データに名前をつけることによって、値の意味を明確化できます。
Defining and Instantiating Structs - The Rust Programming Language
基本的な使用方法
下記のように struct
により構造体 User
を定義し、 let user = User
によってUserの実体化をすることにより、 user.username
として使用することができます。
struct User { username: String, sign_in_count: u64, active: bool, } fn main() { let user = User { username: String::from("User Name"), sign_in_count: 10, active: false, }; println!("user.username -> {}", user.username); println!("user.sign_in_count -> {}", user.sign_in_count); println!("user.active -> {}", user.active); }
user.username -> User Name user.sign_in_count -> 10 user.active -> false
構造体であるため、フィールド名ではなく順序による実体化は許されません。
struct User { username: String, sign_in_count: u64, active: bool, } fn main() { let user = User { String::from("User Name"), 10, false, }; println!("user.username -> {}", user.username); println!("user.sign_in_count -> {}", user.sign_in_count); println!("user.active -> {}", user.active); }
error: expected one of `,` or `}`, found `::` --> src/demo.rs:8:9 | 7 | let user = User { | ---- while parsing this struct 8 | String::from("User Name"), | ^^ expected one of `,` or `}` here error: expected identifier, found `10` --> src/demo.rs:9:3 | 7 | let user = User { | ---- while parsing this struct 8 | String::from("User Name"), 9 | 10, | ^^ expected identifier error: expected identifier, found keyword `false` --> src/demo.rs:10:3 | 7 | let user = User { | ---- while parsing this struct ... 10 | false, | ^^^^^ expected identifier, found keyword | help: you can escape reserved keywords to use them as identifiers | 10 | r#false, | ^^^^^^^ error[E0063]: missing fields `active`, `sign_in_count`, `username` in initializer of `User` --> src/demo.rs:7:13 | 7 | let user = User { | ^^^^ missing `active`, `sign_in_count`, `username`
mutableな使用方法
user
をmutableにすることにより、後続処理で user.username = ???
により、 username
を変更することができます。
struct User { username: String, sign_in_count: u64, active: bool, } fn main() { let mut user = User { username: String::from("User Name"), sign_in_count: 10, active: false, }; user.username = String::from("Mutable User Name"); println!("user.username -> {}", user.username); println!("user.sign_in_count -> {}", user.sign_in_count); println!("user.active -> {}", user.active); }
user.username -> Mutable User Name user.sign_in_count -> 10 user.active -> false
関数を用いた実体化
下記のように関数を用いて実体化をすることも可能です。これにより、 active
を固定化するなど、コーディングの高速化が図れそうですね。
struct User { username: String, sign_in_count: u64, active: bool, } fn main() { let user = build_user(String::from("Build User Name")); println!("user.username -> {}", user.username); println!("user.sign_in_count -> {}", user.sign_in_count); println!("user.active -> {}", user.active); } fn build_user(username: String) -> User { User { username: username, sign_in_count: 9, active: true, } }
user.username -> Build User Name user.sign_in_count -> 9 user.active -> true
短縮形を用いた実体化
パラメータ名とフィールド名が同一である場合は struct
のフィールド名を短縮することが可能です。
struct User { username: String, sign_in_count: u64, active: bool, } fn main() { let user = build_user(String::from("Build User Name")); println!("user.username -> {}", user.username); println!("user.sign_in_count -> {}", user.sign_in_count); println!("user.active -> {}", user.active); } fn build_user(username: String) -> User { User { username, sign_in_count: 9, active: true, } }
user.username -> Build User Name user.sign_in_count -> 9 user.active -> true
短縮系は関数をかえさず、同一スコープ内でも使用することが可能です。
struct User { username: String, sign_in_count: u64, active: bool, } fn main() { let username = String::from("User Name"); let sign_in_count = 8; let active = false; let user = User { username, sign_in_count, active, }; println!("user.username -> {}", user.username); println!("user.sign_in_count -> {}", user.sign_in_count); println!("user.active -> {}", user.active); }
user.username -> User Name user.sign_in_count -> 8 user.active -> false
他のインスタンスを利用した実体化
下記の ..user1
のように、他のインスタンスを利用して、差分のみを記述することで実体化をすることが可能です。
struct User { username: String, sign_in_count: u64, active: bool, } fn main() { let user1 = User { username: String::from("User1 Name"), sign_in_count: 7, active: false, }; let user2 = User { username: String::from("User2 Name"), ..user1 }; println!("user1.username -> {}", user1.username); println!("user1.sign_in_count -> {}", user1.sign_in_count); println!("user1.active -> {}", user1.active); println!("user2.username -> {}", user2.username); println!("user2.sign_in_count -> {}", user2.sign_in_count); println!("user2.active -> {}", user2.active); }
user1.username -> User1 Name user1.sign_in_count -> 7 user1.active -> false user2.username -> User2 Name user2.sign_in_count -> 7 user2.active -> false
タプル構造を利用した構造体
例えば、色の構造体を利用する場合、フィールド名を r,g,b,a
として考えて記載すると、下記のようになります。
struct Color { r: u8, g: u8, b: u8, a: u8, } fn main() { let color = Color { r: 255, g: 254, b: 253, a: 252, }; println!("({}, {}, {}, {})", color.r, color.g, color.b, color.a); }
(255, 254, 253, 252)
フィールド名を利用せずタプル構造とした場合、下記のように記述ができます。
struct Color(u8, u8, u8, u8); fn main() { let color = Color(255, 254, 253, 252); println!("({}, {}, {}, {})", color.0, color.1, color.2, color.3); }
(255, 254, 253, 252)
出力方法
An Example Program Using Structs - The Rust Programming Language
下記のように、単純に出力しようとすると、std::fmt::Display
ではないためコンパイルエラーとなります。
struct Rectangle { width: u32, height: u32, } fn main() { let rect = Rectangle { width: 30, height: 50, }; println!("rect is {}", rect); }
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display` --> src/demo.rs:12:25 | 12 | println!("rect is {}", rect); | ^^^^ `Rectangle` cannot be formatted with the default formatter | = help: the trait `std::fmt::Display` is not implemented for `Rectangle` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required by `std::fmt::Display::fmt
そのため、 #[derive(Debug)]
を利用して、構造体に Debug
特性を導出して、デバッグフォーマットを利用できるようにすると共に、 {:?}
による成形をすることで、出力させることが可能です。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect = Rectangle { width: 30, height: 50, }; println!("rect is {:?}", rect); }
rect is Rectangle { width: 30, height: 50 }
{:?}
ではなく {:#?}
を利用することで、もし構造体が長くなったとしても、見やすくなります。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect = Rectangle { width: 30, height: 50, }; println!("rect is {:#?}", rect); }
rect is Rectangle { width: 30, height: 50, }
メソッド構文
Vuexの getters
のように、構造体に対して関数をもたせることが可能です。
例えば、出力テストで利用した Rectangle
を用いると、 impl
(実装)により関数を定義して利用することが可能です。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect = Rectangle { width: 30, height: 50, }; println!("rect is {:#?}", rect); println!("area -> {}", rect.area()); }
rect is Rectangle { width: 30, height: 50, } area -> 1500
また、同一 impl
に複数の関数をもたせたり( height_squared
)、
別 impl
を利用したり( width_squared
)、
関数内で関数を利用したり( demo
)することも可能です。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn height_squared(&self) -> u32 { self.height * self.height } fn demo(&self) -> u32 { self.height_squared() + self.width_squared() } } impl Rectangle { fn width_squared(&self) -> u32 { self.width * self.width } } fn main() { let rect = Rectangle { width: 30, height: 50, }; println!("rect is {:#?}", rect); println!("area -> {}", rect.area()); println!("width_squared -> {}", rect.width_squared()); println!("height_squared -> {}", rect.height_squared()); println!("demo -> {}", rect.demo()); }
rect is Rectangle { width: 30, height: 50, } area -> 1500 width_squared -> 900 height_squared -> 2500 demo -> 3400
最後に
構造体については、ScalaでDDDの考えを取り入れていた時に、 Entity周りで大変お世話になった存在なので、 Rustにも存在していて重宝したいです。