Rust:Concise Control Flow with if let
Concise Control Flow with if let - The Rust Programming Language
if let
記法は、あるパターンにマッチした時だけハンドリングし、残りは無視するような時に使用できる。
match
let config_max = Some(3u8); match config_max { Some(max) => println!("The maximum is configured to be {max}"), _ => (), }
if let
let config_max = Some(3u8); if let Some(max) = config_max { println!("The maximum is configured to be {}", max); }
- パターンと式を
=
で分ける。 - 上記の例では、パターンが
Some(max)
でmax
にSome
の中の値がバインドされる。
if let
を使うことで、タイピング量が減り、インデントも減り、定型コードも減るが、match
が強制していたチェックを失うことになる。どちらを使用するかはトレードオフの関係である。
if let
はelse
も使用できる。
let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state); } else { count += 1; }
Rust:The match Control Flow Construct
The match Control Flow Construct - The Rust Programming Language
Rustにはmatch
と呼ばれる、値をパターンに基づいて処理する強力な制御フロー構造がある。
パターンは、リテラル値、変数名、ワイルドカード、その他多くのもので構成することができる。
参照:Patterns and Matching
enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => { println!("Lucky penny!"); 1 } Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } }
match
キーワードに続いて式を記述する。if式はboolean型に評価できる必要があるが、あらゆる型を取りうるところが大きな違い。match
armsは、パターン部分とコードで構成され、=>
オペレーターがパターンとコードを分ける- armは
,
カンマで区切るが{}
カーリーブラケットを使用した場合は、省略可能である - 各armに関連付けられたコードは式であり、match全体の戻り値となる
値に束縛されるパターン
match armsの有用な機能として、パターンにマッチした値を束縛できることである。
#[derive(Debug)] enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!("State quarter from {:?}!", state); 25 } } }
Optionによるマッチング
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);
- Some(5)はSome(i)にマッチし、
i
にはSome
の値である5
がバインドされ、新しいSome(i+5)
が作成される
Matchは完全である
Rustは全てのケースをカバーしているかどうか検知する。
Catch-allパターンとアンダースコアプレースホルダー
let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => (), } fn add_fancy_hat() {} fn remove_fancy_hat() {}
- それ以外にの値にマッチする場合は
other
を使用する - パターンは順番に評価されるため、catch-all armsは最後に記述する必要がある
- catch-allパターンで、その値を使用しない場合は、
_
を使用することができる - ユニット値(空のタプル)
()
を指定することで、何もしない状態を表現できる
Rust:Defining an Enum
Defining an Enum - The Rust Programming Language
構造体は関連するフィールドとデータをグループ化する1つの方法である。
列挙型(enum)は取りうる値のセットを定義する。
enum IpAddrKind { V4, V6, }
Enumの値
let four = IpAddrKind::V4; let six = IpAddrKind::V6;
enumには直接データを持たせることができ、さらに値ごとに異なるデータを関連付けることが可能
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
標準ライブラリのIpAddrは、それぞれの値に異なる構造体を持たせている。
enumには様々なデータをもたせることが可能である。
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
- Qute:データなし
- Move:構造体のような名前付きフィールド
- Write:単一のString型
- ChangeColor:3つの整数型
上記を構造体で表現した場合以下のようになるが、これらの全ての型に対応するような関数を定義することは簡単ではない。
struct QuitMessage; // unit struct struct MoveMessage { x: i32, y: i32, } struct WriteMessage(String); // tuple struct struct ChangeColorMessage(i32, i32, i32); // tuple struct
また、enumもメソッドを定義することができる。
impl Message { fn call(&self) { // method body would be defined here } } let m = Message::Write(String::from("hello")); m.call();
Option型とNull値に対する優位性
Option型は標準ライブラリで定義されているenumで、何某かの値を持つか、何も無いかを一般化する。
Rustは他の多くの言語が持っているNullがなく、nullかnull以外かをOption<T>
でコード化する。
enum Option<T> { None, Some(T), }
Option<T>
は通常のenumであり、Some(T)
とNone
もOption<T>
の取りうる値である。
<T>
記法は、ジェネリック型引数で、今はSome
があらゆる型のデータを1つ保持することができ、T
マークはOption型全体として、実際の型に置き換わるとだけ理解しておけば良い。
let some_number = Some(5); let some_char = Some('e'); let absent_number: Option<i32> = None;
some_number
はOption<i32>
型で、some_char
はOption<char>
型で異なる型になる。- Rustは
Some
に渡された実際の値で型を推測できるが、absent_number
は推測できないため、型を指定する必要がある Some
の場合は値があることを、None
の場合は値がないことを表す。
何がnullよりよいのか?
Option<T>
とT
は異なる型になり、コンパイラはOption<T>
の値を確実に有効な値としては使用しない。
// i8とOption<i8>は異なる型のため、加算できないのでコンパイルエラーになる let x: i8 = 5; let y: Option<i8> = Some(5); let sum = x + y;
Rustではi8
のような型の場合、コンパイラーが有効な値であることを保証してくれるため、nullチェックの必要がない。
Option<i8>
のような型の場合のみ、値がない場合の考慮が必要で、そのようなケースをハンドリングしているかどうかをコンパイラーは確認する。
言い換えると、T
型の処理をするには、Option<T>
をT
に変換する必要があり、その時、値がない場合のケースの処理も強制させるため、not nullエラーが発生しない。
Rust:Method Syntax
Method Syntax - The Rust Programming Language
メソッドは関数に似ているが、以下の点が異なる
メソッド定義
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!("{}", rect1.area()); }
- メソッドの第1引数は、所有権を取る
self
、不変参照の&self
、可変参照の&mut self
にすることができる - フィールド名と同じ名前のメソッドも可能。その場合、
()
の有無でメソッド呼び出しか否かをコンパイラーは判断する - フィールド名と同名のメソッドは、通常はgetterとして定義する。getterにすることでフィールドを読み取り専用にすることができる。
Where’s the -> Operator?
- Rustには自動参照・参照外しという機能があり、メソッド呼び出しはこの振る舞いをする数少ない場所である
- object.something()とすると、Rustは自動的に、
&
、&mut
、または*
をつける - 以下は同じ動きをする
p1.distance(&p2); (&p1).distance(&p2);
より多くのパラメータをもつメソッド
self
パラメータのあとに、複数のパラメータを付けることができ、関数と同様に動作する。
関連関数
impl
ブロックの中に定義するが、self
パラメータを持たない関数のこと。
インスタンスを必要としないため、メソッドではない。例)String::from
関数。
関連関数はコンストラクタとして使用されることが多い。また関数名にはnew
が使われることが多いが、言語に組み込まれたものではない。
impl Rectangle { fn square(size: u32) -> Self { Self { width: size, height: size, } } }
Self
キーワードはimpl
キーワードの関数内に現れると、戻り値の型を表す- 関連関数の呼び出しは
let sq = Rectangle::square(3);
のように::
を使用する
複数のimplブロック
複数のimplブロックを定義することが可能。