Not logged in. Login

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, 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.


Dynamic Types in Rust

In, 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

Cost of Dynamic Dispatch

Once your functions are working, benchmarking code has been provided in Try it:

cargo bench

Add a comment at the top of your indicating what the apparent cost of dynamic dispatch in Rust is.


Submit your files through CourSys for Exercise 10.

Updated Fri April 28 2023, 10:19 by ggbaker.