はじめに
Rust ではプログラムが予期せぬエラーに遭遇した状況を パニック という。
パニックは意図的に panic! で発生させることもできる。
またパニックに陥ったプログラムは復旧することができない。
(厳密には通常、パニックに陥ったスレッドは終了し、プログラム全体も終了する。ただし特殊な仕組みを使えば、スレッドごとにパニックを捕捉して処理を続けることもできる。)
Rust では Result<T, E> を利用することによってパニックを回避することができる。
Result<T, E> の使い方を理解する上で最低限以下の知識が必要になる。
Enum
列挙型とも呼ばれる。
enum Fruit {
Apple,
Banana,
Orange,
}
列挙子へのアクセスは :: を使用する。
enum Fruit {
Apple,
Banana,
Orange,
}
let x: Fruit = Fruit::Apple;
let y: Fruit = Fruit::Banana;
let z: Fruit = Fruit::Orange;
値を保持する列挙子
列挙子は値を持つことができる。
enum Fruit {
Apple(String),
Banana(i32),
Orange(String, i32),
}
let x: Fruit = Fruit::Apple(String::from("apple"));
let y: Fruit = Fruit::Banana(10);
let z: Fruit = Fruit::Orange(String::from("orange"), 20);
列挙子には構造体を持たせることもできる。
enum Fruit {
Apple { x: i32, y: String },
}
let x: Fruit = Fruit::Apple {
x: 10,
y: String::from("apple"),
};
Option<T>
Rust の標準ライブラリには Option<T> という Enum が用意されている。
Option<T> は値が存在するかどうかを表す型である。
値があるときは Some(T)、値がないときは None によって表現する。(None は「エラー」ではなく「値が存在しない」ことを意味する)。
enum Option<T> {
Some(T),
None,
}
fn hello(x: i32) -> Option<String> {
if 0 < x {
Some(String::from("hello"))
} else {
None
}
}
Result<T, E>
Result<T, E> は処理の成否を表す型である。
成功時は Ok(T) を、失敗時は Err(E) を保持する。
Option<T> が「有無」だけを区別するのに対し、Result<T, E> は失敗した理由を E 型で表現することができる。
enum Result<T, E> {
Ok(T),
Err(E),
}
fn hello(x: i32) -> Result<String, i32> {
if 0 < x {
Ok(String::from("hello"))
} else {
Err(10)
}
}
match
_ はマッチしないパターンを表す。
fn hello(x: i32) {
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("other"),
}
}
match では、すべてのパターンが網羅されていない場合にコンパイルエラーが起きる。
fn hello(x: i32) {
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
// 網羅されていないパターンがあるためコンパイルエラーが発生する
}
}
これはプログラミングをする上で、コンパイラがパターンチェクを保証してくれるという点で利点として説明されている。
The power of match comes from the expressiveness of the patterns and the fact that the compiler confirms that all possible cases are handled.
パターンの表現力、すべてのケースが処理されていることをコンパイラが保証してくれる点がmatchの強みである。
Rust knows that we didn’t cover every possible case, and even knows which pattern we forgot! Rust doesn’t compile our code until we fix the problem.
すべてのケースが網羅されていない場合、Rust はそのことを知っているし、どのパターンを忘れたかさえも知っている。さらにそれが直るまでは、コンパイルしない。
The match Control Flow Construct
match の戻り値
match 構文の 1 => println!("one"), 部分は arm と呼ばれ、各 arm に紐付けられたコード(=> の右部分)は 式 である。
そのため、マッチしたアームの「式」の結果を match 式の戻り値とすることができる。
fn hello(x: i32) -> String {
match x {
1 => String::from("one"),
2 => String::from("two"),
3 => String::from("three"),
_ => String::from("other"),
}
}
let x: String = hello(10);
複数の処理を実行したい場合には {} を使用する。
fn hello(x: i32) -> String {
match x {
1 => {
println!("hello");
String::from("one")
}
_ => String::from("other"),
}
}
マッチしない _ ときに何も処理を実行したくない場合、ユニット型 を返せば良い。
fn hello(x: i32) {
match x {
1 => println!("hello"),
_ => (),
}
}
Enum x match
前述の通り Enum は値を持つことができる。
値をもった Enum と match を組み合わせると、match 構文の中で Enum の値を利用することができる。
enum Fruit {
Apple(String), // 値を持つ Enum
Banana,
Orange,
}
fn hello(x: Fruit) {
match x {
Fruit::Apple(data) => println!("data is {data}"), // 値に任意の変数名でアクセスできる
Fruit::Banana => println!("banana"),
Fruit::Orange => println!("orange"),
}
}
fn main() {
hello(Fruit::Apple(String::from("apple")));
}
比較に対して列挙子の値を利用することもできる。
以下の例では Apple(10) にはマッチするのに対して、Apple(11) に対してはマッチしない。
enum Fruit {
Apple(i32), // 値を持つ Enum
Banana,
Orange,
}
fn hello(x: Fruit) {
match x {
Fruit::Apple(10) => println!("apple"), // 特定の値へのマッチ
Fruit::Banana => println!("banana"),
Fruit::Orange => println!("orange"),
_ => (),
}
}
Option<T> と match を組み合わせると以下のようなことができる。
fn hello(x: i32) -> Option<i32> {
if x == 1 { Some(10) } else { None }
}
fn main() {
match hello(1) {
None => println!("none"),
Some(data) => println!("data is {data}"),
}
}
if x match
match を使った構文で、特定の値かそれ以外かで処理を分けたい場合に、Rust には if let というシンタックスシュガーがある。
enum Fruit {
Apple(i32), // 値を持つ Enum
Banana,
Orange,
}
fn hello(x: Fruit) {
match x {
Fruit::Apple(10) => println!("hello"), // 特定の値へのマッチ
_ => (),
}
}
fn hello(x: Fruit) {
if let Fruit::Apple(10) = x { // 上記のシンタックスシュガー
println!("hello");
}
}
上記の例は以下のように通常の if でも表現できるため、メリットが感じられない場合もあるが、処理の中で列挙子が持つ値にアクセスできるなど、より柔軟な処理が可能という利点がある。
fn hello(x: Option<i32>) {
match x {
Some(10) => println!("hello"),
_ => (),
}
}
// if let 構文
fn hello(x: Option<i32>) {
if let Some(10) = x {
println!("hello");
}
}
// 通常の if 文
fn hello(x: Option<i32>) {
if x == Some(10) {
println!("hello");
}
}
fn hello(x: Option<i32>) {
match x {
Some(data) => println!("data is {data}"),
_ => (),
}
}
// if let 構文なら表現できる
fn hello(x: Option<i32>) {
if let Some(data) = x {
println!("data is {data}")
}
}
// 通常の if 文では表現できない(コンパイルエラー)
fn hello(x: Option<i32>) {
if x == Some(data) {
println!("data is {data}")
}
}
参考