1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2024-12-15 22:37:28 +02:00

Update expression-evaluation exercise: more patterns, more enums (#1582)

This modifies the exercise to lean more into interesting `match`
statements. It also uses the standard `Result` type, based on feedback
that students could understand it sufficiently at this point in the
course.

Addresses #1565.
This commit is contained in:
Dustin J. Mitchell 2023-12-14 10:22:28 -05:00 committed by GitHub
parent 302a03bbe3
commit 085cbf2c1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 38 deletions

View File

@ -4,31 +4,18 @@ minutes: 30
# Exercise: Expression Evaluation
Let's write a simple recursive evaluator for arithmetic expressions. Start with
an enum defining the binary operations:
```rust
{{#include exercise.rs:Operation}}
{{#include exercise.rs:Expression}}
{{#include exercise.rs:Res}}
{{#include exercise.rs:eval}}
todo!()
}
{{#include exercise.rs:tests}}
```
Let's write a simple recursive evaluator for arithmetic expressions.
The `Box` type here is a smart pointer, and will be covered in detail later in
the course. An expression can be "boxed" with `Box::new` as seen in the tests.
To evaluate a boxed expression, use the deref operator to "unbox" it:
To evaluate a boxed expression, use the deref operator (`*`) to "unbox" it:
`eval(*boxed_expr)`.
Some expressions cannot be evaluated and will return an error. The `Res`
type represents either a successful value or an error with a message. This is
very similar to the standard-library `Result` which we will see later.
Some expressions cannot be evaluated and will return an error. The standard
[`Result<Value,
String>`](https://doc.rust-lang.org/std/result/enum.Result.html) type is an
enum that represents either a successful value (`Ok(Value)`) or an error
(`Err(String)`). We will cover this type in detail later.
Copy and paste the code into the Rust playground, and begin implementing
`eval`. The final product should pass the tests. It may be helpful to use
@ -42,5 +29,18 @@ temporarily with
fn test_value() { .. }
```
If you finish early, try writing a test that results in an integer overflow.
How could you handle this with `Res::Err` instead of a panic?
If you finish early, try writing a test that results in division by zero or
integer overflow. How could you handle this with `Result` instead of a panic?
```rust
{{#include exercise.rs:Operation}}
{{#include exercise.rs:Expression}}
{{#include exercise.rs:eval}}
todo!()
}
{{#include exercise.rs:tests}}
```

View File

@ -41,31 +41,18 @@ enum Expression {
}
// ANCHOR_END: Expression
// ANCHOR: Res
/// The result of evaluating an expression.
#[derive(Debug, PartialEq, Eq)]
enum Res {
/// Evaluation was successful, with the given result.
Ok(i64),
/// Evaluation failed, with the given error message.
Err(String),
}
// Allow `Ok` and `Err` as shorthands for `Res::Ok` and `Res::Err`.
use Res::{Err, Ok};
// ANCHOR_END: Res
// ANCHOR: eval
fn eval(e: Expression) -> Res {
fn eval(e: Expression) -> Result<i64, String> {
// ANCHOR_END: eval
match e {
Expression::Op { op, left, right } => {
let left = match eval(*left) {
Ok(v) => v,
Err(msg) => return Err(msg),
e @ Err(_) => return e,
};
let right = match eval(*right) {
Ok(v) => v,
Err(msg) => return Err(msg),
e @ Err(_) => return e,
};
Ok(match op {
Operation::Add => left + right,