Lab 1: Rust Basics

Due February 14, 11:59pm

In this lab, we'll be getting some practice writing basic Rust functions using control flow, as well as learning some tools that will help us write good Rust code for the rest of the semester.

Grading Rubric

  • Code is formatted properly using cargo fmt: 5%
  • Code passes cargo clippy without warnings: 20%
  • Code passes our tests: 50%
  • Responses in questionnaire.md: 25%

Table of Contents

Setting up your environment

Since the lab computer don't currently come with Cargo installed, we'll install it ourselves. If you'd like to work on your personal computer, visit https://rustup.rs/ for installation instructions. This will take up about ~1.2GB on your machine, but you can always uninstall it later.

For the lab machines, you'll need to run the following lines in the terminal:

cd
mkdir /scratch/<your-username>/.rustup
ln -s /scratch/<your-username>/.rustup ~/.rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Make sure to replace <your-username> with your Swarthmore username. For instance, you might write

mkdir /scratch/student1/.rustup

if your Swarthmore username were student1. (It isn’t.)

Next, create a directory for where you'll store the repositories for this course:

mkdir cs17sr && cd cs17sr

Then, clone your starter code from your Git repo, ensuring to replace <your-username> with your Swarthmore username as well:

git clone git@github.swarthmore.edu:cs17sr-s23/lab1-<your-username>.git

Running the clone command will cause git to create a directory named e.g. lab1-student1 in your current directory. For editing, you should be using VSCode because it has solid integration with rust-analyzer, a utility that is extremely helpful for writing Rust code. Run the following command to install it as a VSCode extension:

code --install-extension rust-lang.rust-analyzer

Then you can open your repo with VSCode, making sure to replace student1 with your username:

code lab1-student1

If you've done everything correctly, you should see this file tree on the left side of your VSCode window:

src/
├─ lib.rs
├─ tests.rs
.gitignore
Cargo.toml
questionnaire.md

Now you're ready to get started!

Writing basic functions

In src/lib.rs, you'll find a variety of functions with comments explaining what it should do, and it's your task to implement them. Several of these functions can be solved iteratively and recursively, and you should aim to use both of these strategies for at least one function each.

If you're having troubles, that's totally okay! Learning the syntax of a new language is always tricky, so please don't hesitate to ask questions either in person or on Courselore.

Tips

  • When you start, each function will each contain a todo!() statement, which is a placeholder macro that will allow the program to compile but instantly crashes when the function is called. Make sure to remove this when you implement each function!
  • Don't be afraid to use return when you need to return a value that's not the last expression, such as in the middle of a loop.
  • Remember to use let mut for variables you might want to update.
  • Read the instructions and error messages carefully.

Formatting your code

Although Rust doesn't care about whitespace in code similar to C++, the Rust community has generally agreed on formatting guidelines so all Rust programs can have some internal consistency with spacing. In fact, Cargo even comes with a built-in command to format your code according to these guidelines automatically:

cargo fmt

Make sure to run this before your submit your code, as it is worth 5% of your lab grade and is so easy to do!

Styling your code

While cargo fmt can be used to fix up the whitespace on your code, Cargo also comes with a built-in linter called Clippy, which can detect and provide suggestions for patterns that are not idiomatic Rust code. To use Clippy, run the following Cargo command:

cargo clippy

To demonstrate Clippy, take the following function:

#![allow(unused)]
fn main() {
fn add(a: i32, b: i32) -> i32 {
    return a + b;
}
}

Although this function works as expected, cargo clippy will fail because it's more idiomatic to write a + b instead of return a + b;. Clippy is a valuable tool for helping you write more idiomatic programs and catch common antipatterns, so you should become accustomed to taking feedback from it.

Make sure your code passes cargo clippy before submitting your code, as it is worth 20% of your lab grade!

Testing your code

So far, we've seen cargo fmt and cargo clippy. While these tools can make your code look good and Rusty, they don't say much about the correctness of how your code works. For that, we need testing.

Near the top of lib.rs, you'll find these lines:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests;
}

The second line declares a submodule called tests, whose implementation can be found in src/tests.rs, while the first line is an attribute. It tells the compiler to only look at mod tests when compiling with the test flag, otherwise ignore it completely.

Making our way over to src/tests.rs, we find the following at the top of the file:

#![allow(unused)]
fn main() {
mod tests {
use super::*;
}
}

The super part says "things defined in the super module" (src/lib.rs in this case), and the ::* means "in this module, bring everything into scope". This means that functions like is_divisible, defined in src/lib.rs, can be used here in src/tests.rs. If we wanted to call is_divisible without this use line, then we would need to specify the fully-qualified path instead: super::is_divisible, which says "in the super module, I'm taking about the is_divisible function."

Writing tests is straight forward: just define an ordinary function with no parameters and returning () named whatever you want the test to be called. Then, write #[test] above it to tell Rust that it's a test, and put your code inside.

Inside the function, you can use the assert!() macro which will crash your program if a condition is not true. See the examples in its documentation for ideas on how to use.

When writing tests, you should try to keep each test function small and not test too many different things at once. That being said, each test can contain more than a single assert!() statement. For example, a test function for the Fibonacci sequence might consist of several checks that different values in the sequence are properly calculated by the fib function:

#![allow(unused)]
fn main() {
// Dummy function hardcoded to make this test work. Do not copy!
fn fib(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        2 => 1,
        3 => 2,
        4 => 3,
        5 => 5,
        6 => 8,
        _ => unreachable!(),
    }
}
#[test]
fn test_fib() {
    let answers = [0, 1, 1, 2, 3, 5, 8];
    for i in 0..7 {
        assert!(fib(i) == answers[i]);
    }
}
}

To run all your tests, use the following Cargo command:

cargo test

This will run all functions marked as #[test] and display the results.

For this lab, you're required to write one test for each function, but are welcome to write more if you'd like. Note that 50% of your grade on this lab comes from your code passing our tests (hidden from you), so it's important that you test your code to build confidence that it works properly and handles any edge cases if there are any.

Questionnaire

The last part of this assignment is to fill our several short response questions in questionnaire.md.

Feedback

Please fill out this short feedback form.

Submitting

Once you're finished, be sure to verify that:

  • cargo fmt has been run
  • cargo clippy passes
  • cargo test passes

Then you can push your changes with the usual: git add ., git commit -m "your message", and git push.

Congratulations on finishing!