In this exercise, you will learn

  • how to open a file

  • how to handle errors using the Result-type.

  • how to use the Option-type.

  • how to do some elementary file processing (counting, reading line by line).

  • how to navigate the Rust stdlib documentation

  • how to add external dependencies to your project

Both the Option and Result are similar in way. Both have two variants, and depending on what those variants are, the program may continue in a different way.

The Option Type can have the variant Some(<some other type>) or None. It is used, when you have to handle optional values, for example if you want to be able to leave a field of a struct empty, go assign the option type to it. If the field has a value, it is Some(<value>), if it is empty, it is None.

The variants of the Result type are Ok(t) and Err(e). It is used to handle errors. If an operation was successful, Ok(t) is returned. In Ok(t), t can be the empty tuple or a return value. In Err(e), e contains an error message that can be printed.

Both types can be used with the match keyword. The received value is matched on patterns, each leads to the execution of a different expression.

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

Template

Clone the teaching material repository at github.com/ferrous-systems/teaching-material.

Then, start your VSCode in the proper root folder to have Rust-Analyzer working properly.

git clone https://github.com/ferrous-systems/teaching-material
code teaching-material/assignments/files-match-result-assignment/template/

Your code will use the example data found in files-match-result-assignment/template/src/data.

Task 1: Files and Errors

  • Open the file "src/data/content.txt" using File::open and bind the result to the variable f.

  • Find out what type the variable f is. Either your IDE shows you, or you can assign a random type to the variable, and run the program.

  • match the two possible patterns, Ok(file) and Err(e) to an an appropriate expression, for example: println!("File opened") and println!("Error: {}", e)

IDEs often provide a "quick fix" to roll out all match arms quickly

Task 2: Reading Files…​ and Errors

  • import std::io::prelude::*

  • To use the content of the file, bind the result of the match statement to a variable.

    • In the other branch of the match statement, change println!("Error: {}", e) to panic!("Error: {}", e)

  • Read the content of the file into a String. Use Read::read_to_string.

  • Print the entire content of the file using println!

  • Handle all errors (the compiler will warn you if you miss one)

Task 3: Reading files line by line…​ and Errors

  • Construct a BufReader around the file

  • Print the content of the file line by line. The lines()- method returns an Iterator over the file.

  • Print the number of lines the file contains.

  • lines returns the Result-Type, use it to get to the actual String.

  • Filter out the empty lines, and only print the the others. The is_empty method can help you here.

Task 4: Read URLs from file…​ and Errors

  • Add url = "2" to your dependencies section in Cargo.toml

  • Write a function that parses each line using the Url Type: fn parse_url(line: String) → Option<Url>

    • If a line can be parsed successfully, return Some(url), None otherwise

    • In the calling context, only print URLs that parse correctly

    • Test the parse_url function

Help

Typing variables

Variables can be typed by using : and a type.

let my_value: String = String::from("test");

match

All arms of the match tree have to result in the same type.