Exercise 10
Download and extract the code skeleton for this exercise. As last week, the code includes an outline of how to organize your work and some tests.
Reading Files and Handling Errors
In order to explore handling errors in Rust, let's read some files. The goal: read a file with one integer per line, and sum the integers. The sample input file good.txt
is the kind of input we expect (and sums to -20).
But there are several places where things can go wrong any time you read a file. The ones I see here: the file might be missing entirely; it might contain bad text (we're assuming UTF-8 encoded text, and not every sequence of bytes is valid UTF-8); it might contain characters that can't be interpreted as a number.
In files.rs
, write a function sum_file_1
that takes a &std::path::Path
and returns Result<i64, SummationError>
. In this implementation, you must pattern match each Result
and handle it (by returning a Err(SummationError)
). In all error cases, we want the function to return an Err(e)
where e
is a SummationError
. If we can successfully sum the value, we return Ok(sum)
.
Also write sum_file_2
that has the same behaviour, but uses the ?
operator to handle any Result
values you get.
I have provided an implementation of SummationError
so you can SummationError::from(e)
for any error type that should be produced by operations you do here. The ?
operator will use the ::from
implementations as appropriate.
Also provided: good.txt
that is in the format we expect, bad_utf8.txt
that contains invalid UTF-8 bytes, bad_number.txt
that contains a line that isn't convertible to an integer.
Hints:
Dynamic Types in Rust
In dynamic.rs
, I have provided a trait Shape
and two types that implement it: Circle
and Rectangle
.
Write functions that take vectors of shapes and decide if any of the shapes have a area equal to zero. We want to test dynamic dispatch in Rust, so we want functions that work on vectors of a single type (both Circle
and Rectangle
), and one that works on a vector of any values that implement Shape
. That is:
pub fn any_circle_zero_area(shapes: &Vec<Box<Circle>>) -> bool {…}
pub fn any_rectangle_zero_area(shapes: &Vec<Box<Rectangle>>) -> bool {…}
pub fn any_shape_zero_area(shapes: &Vec<Box<dyn Shape>>) -> bool {…}
Each of these can be (but is not required to be) a single line: shapes.iter().fold(…)
.
We also would like to be able to generate all three versions of Vec<Box<???>>
for testing. You can the provided ::random()
functions to create the structs: it will ensure no zero-area shapes, so our tests have consistent running time (because depending on your implementation, they might exit early if they find a zero).
// generate 2*n Circles
pub fn make_circle_vec(n: usize) -> Vec<Box<Circle>> {…}
// generate 2*n Rectangles
pub fn make_rectangle_vec(n: usize) -> Vec<Box<Rectangle>> {…}
// generate n Circles and n Rectangles
pub fn make_mixed_vec(n: usize) -> Vec<Box<dyn Shape>> {…}
Some tests have been included for these functions in tests.rs
.
Cost of Dynamic Dispatch
Once your functions are working, benchmarking code has been provided in dynamic_benchmarks.rs
. Try it:
cargo bench
Add a comment at the top of your dynamic.rs
indicating what the apparent cost of dynamic dispatch in Rust is.
Submitting
Submit your files through CourSys for Exercise 10.