項目1:データ構造を表現するために型システムを用いよう
基本型
符号 あり: i8, i16, i32, i64, i128 符号なし: u8, u16, u32, u64, u128
ターゲットシステムのポインタサイズ(32bit or 64bit)と一致したサイズの整数
符号あり: usize
符号なし: isize
標準の コレクション では、len() の戻り値やインデックスでは usize を用いるのが一般的 bool
f32, f64
ユニット型
()
char
内部的には 4Byte(32 bit)で格納される
型変換 は明示的に行わないとコンパイルエラーになる サイズの大きい整数型から小さい整数型はもちろん、逆もコンパイルエラーになる
code:rs
let x: i32 = 42;
let y: i16 = x;
let x = 42i32;
let y: i64 = x;
変換を行うヘルパメソッドは用意されているが、失敗した場合に対処する(または無視する)ことを強要する
e.g. char ↔ u32
しかし、u32 の値には Unicode スカラ値として不正な値もあるため、u32 から char への変換は失敗する可能性がある
char::from_u32
Option<char> を返す
呼び出し側で失敗した場合の処理が必要
char::from_u32_unchecked
有効な値が入っていると仮定し、その仮定が誤っている場合は 未定義動作 となる 集約型
e.g. [u32; 4]: 4Byte 整数が 4 つ並んだもの
要素の数とそれぞれの型はコンパイル時に決まる
同じ型の要素が複数ある場合、各要素に名前を付けて 構造体 にしたほうが良い e.g. (i32, i32, &'static, str, bool)
構造体: タプルと同様、コンパイル時に定まる複数の型のインスタンスを保持 違いとしては、型全体と個々のフィールドが名前を持つ
タプル構造体
型全体は名前を持つが、個々のフィールドは名前を持たず、s.0 や s.1 のようにアクセスする
code:rs
struct TextMatch(usize, String);
let m = TextMatch(12, "needle".to_owned());
assert_eq!(m.0, 12);
他の言語と同様、相互に排他な値の集合を指定するために用いられる
code:rs
enum HttpResultCode {
Ok = 200,
NotFound = 404,
TeaPot = 418,
}
let code = HttpResultCode::NotFound;
assert_eq!(code as i32, 404);
enum を定義するとその型が生成される
これにより、たとえば bool の引数を取る関数の 可読性 と 保守性 を高めることが可能 定義前
code:rs
print_page(/* both_sides= */ true, /* color= */ false)
定義後
code:rs
pub enum Sides {
Both,
Single,
}
pub enum Output {
BlackAndWhite,
Color,
}
pub fn print_page(sides: Sides, color: Output) {
// ...
}
print_page(Slides::Both, Output::Color);
print_page(Output::BlackAndWhite, Slides::Single); // コンパイルエラー
どちらを使うべきか?
Newtype パターン: 引数が常に bool であることが分かっているケース
enum: 将来他の値を取る可能性があるケース
enum 値に match を用いると、コンパイラはすべてのパターンを考慮することを強制する
code:rs
let msg = match code {
HttpResultCode::Ok => "OK",
HttpResultCode::NotFound => "Not found",
};
フィールド付き enum
Rust の enum の真価は、個々の Variant にデータを付加できる ことにある これにより、enum を 代数データ型(ADT: Algebraic Data Type)として扱うことができる この機能を用いることで、プログラムのデータ構造の 不変条件 を Rust の型システムに エンコード できる 「型で無効な状態を表現できないようにしよう」
これにより、設計者の意図を人間とコンパイラの双方に明確に伝えることが可能 になる
code:rs
use std::collections::{HashMap, HashSet};
pub enum SchedulerState {
Inert,
Pending(HashSet<Job>),
Running(HashMap<CpudId, Vec<Job>>)
}
上記のコードでは、型定義を見ただけで、Job はスケジューラが完全に起動するまでは Pending で、起動したら CPU プール が割り当てられることが分かる この機能以外で同じことを実現するには、コメント を書くしか無い code:rs
pub struct DisplayProps {
pub x: u32,
pub y: u32,
pub monochrome: bool,
// もし monochrome が true ならば、fg_color は (0, 0, 0) でなければならない
pub fg_color: RgbColor,
}
これをフィールド付き enum で表現すると、以下のように書き換えられる
code:rs
pub enum Color {
Monochrome,
Foreground(RgbColor),
}
pub struct DisplayProps {
pub x: u32,
pub y: u32,
pub color: Color,
}
多用される enum 型
特定の値が存在するか(Some(T))、しないか(None)を表す
warning.icon
何らかのコレクションを扱う際には、要素数が 0 のコレクションと、コレクション自体が存在しない状態を区別するかどうかを決定する必要がある
多くの場合では問題になることはないので、Vec<Thing> を用いれば良い
もし区別する必要があるならば、Option<Vec<Thing>> を用いれば良い
String の場合も同様に "" と文字列自体が存在しない状態を区別するかを決定する必要がある
区別しないのであれば、String を用いれば良い
区別する必要があるならば、Option<String> を用いれば良い
「失敗する可能性がある操作の結果は常に Result<T, E> にエンコードしよう」
T: 成功時の結果で、Ok が保持する
E: 失敗時のエラーの詳細で、Err が保持する
これらを用いることで、様々なメリットが受けられる
設計の意図が明確に