記事について
次のTour of Rustを用いて学んだ内容のメモになります。
ジェネリック型とは
ジェネリック型を使用することで、struct
やenum
を部分的に定義することが可能になる。
コンパイル時にコード内での使用状況に応じて、完全に定義されたバージョンを生成する。
Rustでは通常、インスタンスを生成するコードから最終的な型を推論できる。しかし、補助が必要な場合は、::<T>
演算子を使って明示的に指定する。この演算子はturbofish
という名前でも知られている。
// 部分的に定義された構造体型
struct BagOfHolding<T> {
item: T,
}
fn main() {
// 注意: ジェネリック型を使用すると、型はコンパイル時に作成される。
// ::<> (turbofish) で明示的に型を指定
let i32_bag = BagOfHolding::<i32> { item: 42 };
let bool_bag = BagOfHolding::<bool> { item: true };
// ジェネリック型でも型推論可能
let float_bag = BagOfHolding { item: 3.14 };
// 注意: 実生活では手提げ袋を手提げ袋に入れないように
let bag_in_bag = BagOfHolding {
item: BagOfHolding { item: "boom!" },
};
println!(
"{} {} {} {}",
i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item
);
}
値が無いことの表現
他の言語では値が存在しないことを表すnull
キーワードが用いられる。
null
が存在しないRustでは、1つ以上の値をNone
によって代替するパターンが一般的である。
enum Item {
Inventory(String),
// None は項目がないことを表す
None,
}
struct BagOfHolding {
item: Item,
}
Option
Rustには次のようなOption
と呼ばれるジェネリックな列挙型が組み込まれており、null
を使わずにnull許容な値を表現できる。
enum Option<T> {
None,
Some(T),
}
この列挙型はとても一般的で、Some
とNone
を使えばどこでもインスタンスを生成できる。
// 部分的に定義された構造体型
struct BagOfHolding<T> {
// パラメータ T を渡すことが可能
item: Option<T>,
}
fn main() {
// 注意: i32 が入るバッグに、何も入っていません!
// None からは型が決められないため、型を指定する必要があります。
let i32_bag = BagOfHolding::<i32> { item: None };
if i32_bag.item.is_none() {
println!("バッグには何もない!")
} else {
println!("バッグには何かある!")
}
let i32_bag = BagOfHolding::<i32> { item: Some(42) };
if i32_bag.item.is_some() {
println!("バッグには何かある!")
} else {
println!("バッグには何もない!")
}
// match は Option をエレガントに分解して、
// すべてのケースが処理されることを保証できます!
match i32_bag.item {
Some(v) => println!("バッグに {} を発見!", v),
None => println!("何も見付からなかった"),
}
}
Result
Rustには次のようなResult
と呼ばれるジェネリックな列挙型が組み込まれており、失敗する可能性のある値を返せる。
慣例的に、これを用いてエラー処理を行う。
enum Result<T,E> {
Ok(T),
Err(E),
}
このジェネリック型はカンマで区切られた複数のパラメータ化された型を持つことに注意する必要がある。
この列挙型はとても一般的で、Ok
とErr
を使えばどこでもインスタンスを生成できる。
fn do_something_that_might_fail(i:i32) -> Result<f32,String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
fn main() {
let result = do_something_that_might_fail(12);
// match は Result をエレガントに分解して、
// すべてのケースが処理されることを保証できます!
match result {
Ok(v) => println!("発見 {}", v),
Err(e) => println!("Error: {}",e),
}
}
失敗するかもしれないmain
main関数はResultを返すことができる。
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
// main は値を返しませんが、エラーを返すことがあります!
fn main() -> Result<(), String> {
let result = do_something_that_might_fail(12);
match result {
Ok(v) => println!("発見 {}", v),
Err(_e) => {
// エラーをうまく処理
// 何が起きたのかを説明する新しい Err を main から返します!
return Err(String::from("main で何か問題が起きました!"));
}
}
// Result の Ok の中にある unit 値によって、
// すべてが正常であることを表現していることに注意してください。
Ok(())
}
簡潔なエラー処理
Result
はよく用いられるので、RustにはResult
を扱うための強力な演算子?
が用意されている。
次の2つのコードは等価である。
do_something_that_might_fail()?
match do_something_that_might_fail() {
Ok(v) => v,
Err(e) => return Err(e),
}
使用例としては次。
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
fn main() -> Result<(), String> {
// コードが簡潔なのに注目!
let v = do_something_that_might_fail(42)?;
println!("発見 {}", v);
Ok(())
}
やっつけな Option/Result 処理
OptionとResultの両方には、unwrap
と呼ばれる関数があり、簡単に値を取得するのに便利である。
unwrap
は次のことを行う。
-
Option
/Result
内の値を取得する。 - 列挙型が
None
/Err
の場合、panic!
する。
以下の2つのコードは等価である。
my_option.unwrap()
match my_option {
Some(v) => v,
None => panic!("Rustによって生成されたエラーメッセージ!"),
}
同様に、
my_result.unwrap()
match my_result {
Ok(v) => v,
Err(e) => panic!("Rustによって生成されたエラーメッセージ!"),
}
よいRust使い(Rustacean)であるためには、可能な限りmatch
文を使用すべきである。
ベクタ型
最も有用なジェネリック型のいくつかはコレクション型である。
ベクタは構造体Vec
で表される可変サイズのリストです。
マクロVec!
を使用すると、簡単にベクタを生成できる。
Vec
にはメソッドiter()
があり、これによりベクタからイテレータを生成すれば、ベクタを簡単にforループに入れることができる。
fn main() {
// 型を明示的に指定
let mut i32_vec = Vec::<i32>::new(); // turbofish <3
i32_vec.push(1);
i32_vec.push(2);
i32_vec.push(3);
// もっと賢く、型を自動的に推論
let mut float_vec = Vec::new();
float_vec.push(1.3);
float_vec.push(2.3);
float_vec.push(3.4);
// きれいなマクロ!
let string_vec = vec![String::from("Hello"), String::from("World")];
for word in string_vec.iter() {
println!("{}", word);
}
}
ベクタのメモリ
-
Vec
は構造体ですが、内部的にはヒープ上の固定リストへの参照を保持している。 - ベクタはデフォルトの容量で始まり、容量よりも多くの項目が追加された場合、ヒープ上により大きな容量の固定リストを生成して、データを再割り当てする。
まとめ
ジェネリック型がどれだけのちからを発揮できるかを理解できた。