1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-05-20 17:33:31 +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 # Exercise: Expression Evaluation
Let's write a simple recursive evaluator for arithmetic expressions. Start with Let's write a simple recursive evaluator for arithmetic expressions.
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}}
```
The `Box` type here is a smart pointer, and will be covered in detail later in 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. 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)`. `eval(*boxed_expr)`.
Some expressions cannot be evaluated and will return an error. The `Res` Some expressions cannot be evaluated and will return an error. The standard
type represents either a successful value or an error with a message. This is [`Result<Value,
very similar to the standard-library `Result` which we will see later. 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 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 `eval`. The final product should pass the tests. It may be helpful to use
@ -42,5 +29,18 @@ temporarily with
fn test_value() { .. } fn test_value() { .. }
``` ```
If you finish early, try writing a test that results in an integer overflow. If you finish early, try writing a test that results in division by zero or
How could you handle this with `Res::Err` instead of a panic? 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_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 // ANCHOR: eval
fn eval(e: Expression) -> Res { fn eval(e: Expression) -> Result<i64, String> {
// ANCHOR_END: eval // ANCHOR_END: eval
match e { match e {
Expression::Op { op, left, right } => { Expression::Op { op, left, right } => {
let left = match eval(*left) { let left = match eval(*left) {
Ok(v) => v, Ok(v) => v,
Err(msg) => return Err(msg), e @ Err(_) => return e,
}; };
let right = match eval(*right) { let right = match eval(*right) {
Ok(v) => v, Ok(v) => v,
Err(msg) => return Err(msg), e @ Err(_) => return e,
}; };
Ok(match op { Ok(match op {
Operation::Add => left + right, Operation::Add => left + right,