Rustなんもわからん
Ruistなんも分かりません。わからないのでおててを動かしていきます。
今回もRustのチュートリアル(bookなるもの)を見ていきます。
英語版とその日本語訳版がありますが、今回は日本語訳版を見ていきたいと思います。
(今週は省エネ記事です。)
他の参考資料は以下の通りです。
チュートリアルを読む会
チュートリアルをひたすら読んで、内容をまとめていきます。
今回は6章までです。
5.構造体
struct
または構造体は、意味のあるグループを形成する複数の関連した値をまとめて名前を付けられる独自のデータ型です。
構造体の定義
構造体は以下のように定義されます。
// 構造体の定義
struct User {
// 変数名: 型タイプ の形で定義できる
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
ここで定義した構造体のインスタンスを生成するには、以下のようにする必要があります。
// 構造体インスタンスの生成
let user1 = User {
// 変数名: データのじゅん
// 順番は変更しててもOK
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
また、インスタンスの持つ値を可変にする場合は、let mut user1
のようにする必要があります。
他のインスタンスからインスタンスを生成する場合は以下のようにすることで行えます。
// 全ての値を明示して作成する場合
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
// user1.activeの形で値を引っ張ってこれる。
sign_in_count: user1.sign_in_count,
};
// 構造体更新記法を使う場合。
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
// 未定義のものはuser1のものを使用するという書き方。
..user1
};
タプル構造体
構造体名は持つものの、フィールドに紐づけられた名前がなく、むしろフィールドの型だけのタプル構造体と呼ばれるものを定義できます。
// 構造体の名前とデータ型だけ定義
fn main() {
// タプル構造体定義
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
// タプル構造体の定義
let black = Color(0, 2, 4);
let origin = Point(0, 0, 0);
// 分配
let Color(x, y, z) = black;
println!("{}", x);
println!("{}", y);
println!("{}", z);
// .を使っての抽出
println!("{}", black.1)
}
この時、blackとoriginは異なるタプル構造体インスタンスであり、別の型扱いになります。内部にアクセスする場合は、分配するなり、"."なりでアクセスできます。
構造体を使ったプログラム例
構造体を使うことで、データにラベル付けを行うことが出来ます。
// 長方形構造体の定義
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// インスタンス生成
let rect1 = Rectangle { width: 30, height: 50 };
// 長方形の面積を計算
println!(
"The area of the rectangle is {} square pixels.",
// 借用した値で面積を計算
area(&rect1)
);
}
// Rectangle構造体から面積を計算する関数
// 借用することで、所有権を持たない
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
構造体のフィールドの値を確認するには
構造体中のフィールドの値を直接確認したい場面がありますが、そのままではprintln!
によって確認することが出来ません。
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
// rect1は{}です
println!("rect1 is {}", rect1);
}
Compiling playground v0.0.1 (/playground)
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
--> src/main.rs:10:29
|
10 | println!("rect1 is {}", rect1);
| ^^^^^ `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
このエラーに従って、{:?}
としてみても、以下のようになってしまいます。
Compiling playground v0.0.1 (/playground)
error[E0277]: `Rectangle` doesn't implement `Debug`
--> src/main.rs:10:31
|
10 | println!("rect1 is {:?}", rect1);
| ^^^^^ `Rectangle` cannot be formatted using `{:?}`
|
= help: the trait `Debug` is not implemented for `Rectangle`
= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Rectangle` with `#[derive(Debug)]`
|
1 + #[derive(Debug)]
2 | struct Rectangle {
以上のことから、デバック用の情報を出力する機能を構造体で使えるようにするには、構造体定義の直前に#[derive(Debug)]
を追加する必要があります。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {:?}", rect1);
}
> rect1 is Rectangle { width: 30, height: 50 }
// {:?}を{:#?}にすると、以下のように表示される
rect1 is Rectangle {
width: 30,
height: 50
}
結論としては、デバック用に構造体のフィールドの値を表示させるには、構造体定義の際に#[derive(Debug)]
を加える必要があります。
構造体にメソッドを定義する
構造体に対して関数を定義するには、impl(implementation)ブロックを使います。
// 構造体の定義
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
// 構造体を使った関数の定義
// impl 構造体名 で定義可能
impl Rectangle {
// impl Rectangleという文脈内に存在するので、&selfでOK
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
引数を増やす場合は以下のように定義できます。
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
// 構造体二つを引数としてとることが出来る
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 };
// rect1にrect2ははまり込むか?
// 構造体インスタンス.関数(&引数)で使える。
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
まとめ
- 構造体定義による意味づけ
- implを使っての構造体関数の定義
6.Enumとパターンマッチング
列挙型(enum)は、とりうる値を列挙することで、型を定義させてくれます。ここでは、enumとOption、そしてif letについてまとめていきます。
Enumの定義とインスタンス生成
以下のようにして、Enumの定義と、インスタンス生成を行うことが出来ます。
// 列挙型の定義
enum IpAddrKind {
V4,
V6,
}
// IpAddrKind::、とすることで、データ型をIpAddKindにできる
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
// IpAddrKindの型なら受けられるようにする。
// これで、four, sixの両方を受けられる。
fn route(ip_type: IpAddrKind) { }
enum列挙子内には、任意のデータを格納することが出来ます。
enum Message {
Quit, // 紐づけデータなし
Move { x: i32, y: i32 }, // 匿名構造体
Write(String), // Stringオブジェクト
ChangeColor(i32, i32, i32), // 三つのi32
}
Option型とNull
Rustには、nullがありませんが、値が存在するか不在化という概念は存在し、以下のように定義されています。
enum Option<T> {
Some(T), // 任意型のデータを一つだけ持つ
None,
}
この定義は有益すぎて、初期化処理にすら含まれているため、Option::sum
ではなく、sum
だけで用いることが出来ます。
この型において注意すべきなのは、Option<T>
とT
では別の型であり、以下のような計算はできないということです。
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y; // 型エラー
match制御
Rustには、一連のパターンに対して値を比較し、マッチしたパターンに応じてコードを実行させてくれる、matchという演算子があります。
例えば、以下のように使用できます。
// enumの定義 Coinに対して複数パターンを定義
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
// coinがCoinのどれに一致するかで分岐
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},// 長くてもこのように書ける
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Optionとのマッチ
Option<T>
の中身を使う場合はこのようになります。
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
Optionの中身がない場合はNone、ある場合は値に1を加えることになります。
matchの網羅性
matchでは、全ての場合を網羅する必要があります。なので、以下のような場合ではエラーになります。
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
error[E0004]: non-exhaustive patterns: `None` not covered
(エラー: 包括的でないパターン: `None`がカバーされてません)
-->
|
6 | match x {
| ^ pattern `None` not covered
今回の場合だと、OptionがNoneの場合が記述されていません。そうすると、このようにエラーになります。
しかしながら、明示した条件以外、のような条件を使いたい場合があります。その場合は、_
を使うことで行えます。
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (), // u8で1,3,5,7以外の全てでmatch
}
if letを使っての制御フロー
matchでは全条件を網羅する必要がありますが、それでは過剰な場合があります。例えば、一条件だけ一致するようにする場合は、if let
を用いることが出来ます。
if let Some(3) = some_u8_value {
println!("three");
}
if let深堀
そもそも、if letでは何をしているんでしょうか?例えば、ifではダメだったのでしょうか?
まず問題となるのが、letです。
let PATTERN = EXPRESSION;
// EXPRESSIONとは、何らかの値を返す「式」のこと
// PATTERNとは、値がマッチするか否かに用いられる「パターン」のこと
letは変数定義と習いましたが、実際はそうではなく、 「右辺の式(EXPRESSION)が返す値を左辺のパターン(PATTERN)に当てはめ、PATTERNに従って変数に値を束縛する」 というものだそうです。
なので、let自体にパターン判定の意味があり、それを条件分岐に用いているそうです。
詳しくは、以下の参考資料を見てみてください。
全体まとめ
今週は構造体とenum、matchなどを用いたパターンマッチを学びました。
しかしながら、Option周りの、ジェネリック型であったり、if letの書き方についてはいまいちよくわかりませんでした。Optionだったり、letがパターン一致に関連するものだというあたりは、後々慣れていきたいと思います。