// 著作権 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
Add,
Sub,
Mul,
Div,
}
/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
/// An operation on two subexpressions.
Op { op: Operation, left: Box<Expression>, right: Box<Expression> },
/// A literal value
Value(i64),
}
#[derive(PartialEq, Eq, Debug)]
struct DivideByZeroError;
fn eval(e: Expression) -> Result<i64, DivideByZeroError> {
match e {
Expression::Op { op, left, right } => {
let left = eval(*left)?;
let right = eval(*right)?;
Ok(match op {
Operation::Add => left + right,
Operation::Sub => left - right,
Operation::Mul => left * right,
Operation::Div => {
if right == 0 {
return Err(DivideByZeroError);
} else {
left / right
}
}
})
}
Expression::Value(v) => Ok(v),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_error() {
assert_eq!(
eval(Expression::Op {
op: Operation::Div,
left: Box::new(Expression::Value(99)),
right: Box::new(Expression::Value(0)),
}),
Err(DivideByZeroError)
);
}
#[test]
fn test_ok() {
let expr = Expression::Op {
op: Operation::Sub,
left: Box::new(Expression::Value(20)),
right: Box::new(Expression::Value(10)),
};
assert_eq!(eval(expr), Ok(10));
}
}
Result の戻り値型: 関数シグネチャは Result<i64, DivideByZeroError> を返すように変更されます。この明示的な 型シグネチャにより、呼び出し側は失敗の可能性を処理することを強制されます。
? 演算子: 再帰呼び出しに対して ? を使用します: eval(*left)?。これにより、 エラーがすっきりと伝播されます。eval が Err を返した場合、関数は即座に その Err を返します。Ok(v) を返した場合は、v が left(または right)に代入されます。
Ok でのラップ: 成功した結果は Ok(...) でラップする必要があります。
- ゼロ除算の処理:
right == 0 を明示的にチェックし、 Err(DivideByZeroError) を返します。これにより、元のコードでの panic が置き換えられます。
DivideByZeroError はユニット構造体(フィールドなし)であり、エラーについて 追加で提供するコンテキストがないため、ここではそれで十分であることに触れてください。
? により、エラー処理が例外にほぼ匹敵するほど簡潔になる一方で、 制御フローは明示的なままであることを説明してください。