はじめに
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}")
}
}
参考