はじめに
The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。
- The Rust Programming Languageを読み進める(1〜2章)
- The Rust Programming Languageを読み進める(3章)
- The Rust Programming Languageを読み進める(4章)
5章:構造体を使用して関係のあるデータを構造化する
5.1章:構造体を定義し、インスタンス化する
構造体の基礎
fn main() {
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("{}", user1.email);
user1.email = String::from("anotheremail@example.com");
println!("{}", user1.email);
}
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
- 構造体にはデータの名前と型を定義し、セットでフィールドと呼ぶ
-
user1
の部分はインスタンスを生成している - 構造体のフィールドは書き換え可能だが、一部のフィールドのみ可変にすることは不可
fn main() {
let email = String::from("test@example.com");
let username = String::from("some user");
let user = build_user(email, username);
println!("email: {}, user: {}", user.email, user.username);
}
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn build_user(email: String, username: String) -> User {
User {
email, // email: emailと書かなくて良い
username , // username: usernameと書かなくていい
active: true,
sign_in_count: 1,
}
}
email: test@example.com, user: some user
- 構造体のインスタンスを返す関数が、上記の
build_user
- 関数の仮引数と構造体のフィールド名が同じ場合は、フィールド初期化の書き方を、例えば
email: email
からemail
に省略可能
構造体更新記法
fn main() {
// user1: 構造体のフィールド全てを定義
let user1 = User {
email: String::from("user1@example.com"),
username: String::from("user1"),
active: true,
sign_in_count: 1,
};
// user2: user1の一部の値を使用してインスタンス生成
let user2 = User {
email: String::from("user2@example.com"),
username: String::from("user2"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
// user3: 構造体更新記法による値の再利用
let user3 = User {
email: String::from("user3@example.com"),
username: String::from("user3"),
..user1
};
println!("{:?}", user1);
println!("{:?}", user2);
println!("{:?}", user3);
}
#[derive(Debug)]
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,
}
}
User { username: "user1", email: "user1@example.com", sign_in_count: 1, active: true }
User { username: "user2", email: "user2@example.com", sign_in_count: 1, active: true }
User { username: "user3", email: "user3@example.com", sign_in_count: 1, active: true }
- 他のインスタンスの値を利用して、新たなインスタンスを生成する方法として、
user2
とuser3
の例を示す -
user2
は、user1
の対応するフィールドにそれぞれ値を定義している。対して、user3
は構造体更新記法と呼ばれる方法で、フィールドへの設定を簡潔に書くことができる
タプル構造体
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(127, 127, 127);
// Errors
// let white = Color(255.0, 255, 255);
// let red = Color(255, 0, 0, 0);
println!("{} {} {}", black.0, black.1, black.2);
println!("{} {} {}", origin.0, origin.1, origin.2);
}
- 指定の型で構成されたタプルの構造体を定義可能
- フィールドの型だけが存在しており、
white
やred
の例のように、定義と異なる値の設定は不可
ユニット様構造体
struct UnitStruct;
trait SayOkNg {
fn say_ok(&self) -> String;
fn say_ng(&self) -> String;
}
impl SayOkNg for UnitStruct {
fn say_ok(&self) -> String {
return String::from("Ok");
}
fn say_ng(&self) -> String {
return String::from("Ng");
}
}
fn main() {
let unit_struct = UnitStruct;
println!("{}", unit_struct.say_ok());
println!("{}", unit_struct.say_ng());
}
Ok
Ng
- フィールドのない構造体を定義するのがユニット様構造体。"ユニットさま"では無く"ユニットよう"
- フィールドを持たず、トレイトだけで関数が実装できる。好きに振る舞いを決められるということ?
5.2章:構造体を使ったプログラム例
意味付けされた処理
fn main() {
// 方法0. 変数2つ定義
let width = 30;
let height = 50;
println!("方法0: {}", area(width, height));
// 方法1. タプル
let rect1 = (30, 50);
println!("方法1: {}", area_tuple(rect1));
// 方法2. 構造体
let rect2 = Rectangle {
width: 30,
height: 50,
};
println!("方法2: {}", area_struct(&rect2));
}
// 方法0
fn area(width: u32, height: u32) -> u32 {
width * height
}
// 方法1
fn area_tuple(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}
// 方法2
struct Rectangle {
width: u32,
height: u32,
}
fn area_struct(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
方法0: 1500
方法1: 1500
方法2: 1500
-
width
,height
を渡して掛け算するコード - 方法0:愚直に変数を2つ定義して、それぞれを関数に渡す
- 方法1:タプルを定義して、渡された値を関数の中でバラして計算。添え字の0と1が何であるかを意識しておく必要がある
- 方法2:構造体を定義して計算しており、より意味付けがなされている
-
rect2
では借用しているだけなので、処理の後にもrect2
を使い続けることができる
-
トレイトの導入
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
// #[derive(Debug)] 無しではprintln!できない
// println!("rect1 is {}", rect1);
println!("rect1 is {:?}", rect1);
println!("rect1 is {:#?}", rect1);
}
rect1 is Rectangle { width: 30, height: 50 }
rect1 is Rectangle {
width: 30,
height: 50,
}
- 構造体をそのまま
println!
しようとすると、Rectangle cannot be formatted with the default formatter; try using :? instead if you are using a format string
と怒られる -
println!
の標準はDisplay
という形で整形されて出力される。しかし、構造体だと「どんな形で出力すれば?カンマは必要?」と言ったように曖昧性が含まれるため、構造体にはDisplay
が提供されていない -
{}
に:?
と指定すると、Debug
という出力整形を行わせる指示になる。こちらは、構造体を出力できるためエラーが発生しない- ただし、構造体定義の直前に
#[derive(Debug)]
の注釈が必要
- ただし、構造体定義の直前に
-
{:#?}
と指定すると、1行で出力されていたものが、改行されて出力される
5.3章:メソッド記法
メソッドの定義
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn area_mut(&mut self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("{}", rect1.area());
let mut rect1_mut = Rectangle {
width: 30,
height: 50,
};
println!("{}", rect1_mut.area_mut());
}
1500
1500
- Rustはクラスをサポートしていないが、構造体はメソッドを持つことができ、
impl
を使う- この型のインスタンスで実行可能なことを
impl
ブロックに纏められていることは、メソッドのメリットの1つ -
self
は自身のオブジェクトを示し、自身が持つwidth
,height
を掛け算している
- この型のインスタンスで実行可能なことを
-
&self
だけでは無く、&mut self
と書くこともできるが、インスタンスがその後の処理で使われる想定であれば、rect1_mut
のように可変参照を取ると良い- メソッド (
impl
) を使う時は、メソッドが読み込み専用 (&self
)、書き込みを行う (&mut self
) 、所有権を奪う (self
) を考え使う - 上記のコードだけに限るなら、再利用性は不要なので、
&self
で良い
- メソッド (
引数の多いメソッド
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
Can rect1 hold rect2? true
Can rect1 hold rect3? false
-
&rect2
の部分では不変借用を渡しているが、これは書き込みが必要なく、所有権をmain
に残したいため道理に適っている
関連関数
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let sq = Rectangle::square(3);
println!("area: {}", sq.area());
}
area: 9
-
impl
では、self
を引数に取らない関数を定義できる。関数であり、メソッドでは無い-
Struct::Method
の形で使用する
-
- このパターンは、構造体の新しいインスタンスを作るためのコンストラクタとして利用されることが多い
- メソッドと関連関数は、selfを引数に持つかが異なる点である
複数のimplブロック
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("area: {}", rect1.area());
let rect2 = Rectangle { width: 10, height: 40 };
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}
area: 1500
Can rect1 hold rect2? true
- 構造体は、複数の
impl
ブロックを定義可能 - 上記は分ける意味は無いが、有用になるケースは10章で記載があるとのこと