Hello Rust!

About This Module

This module provides your first hands-on experience with Rust programming. You'll write actual programs, understand basic syntax, and see how Rust's compilation process works. We'll focus on building confidence through practical programming while comparing key concepts to Python.

Prework

Prework Readings

Review this module.

Read the following Rust basics:

Optionally browse:

Pre-lecture Reflections

Before class, consider these questions:

  1. How does compiling code differ from running Python scripts directly?
  2. What might be the advantages of catching errors before your program runs?
  3. How does Rust's println! macro compare to Python's print() function?
  4. Why might explicit type declarations help prevent bugs?
  5. What challenges might you face transitioning from Python's flexibility to Rust's strictness?

Topics

  • Installing Rust
  • Compiled vs Interpretted Languages
  • Write and compile our first simple program

Installing Rust

Before we can write Rust programs, we need to install Rust on your system.

From https://www.rust-lang.org/tools/install:

On MacOS:

# Install Rust via rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Question: can you interpret the shell command above?

On Windows:

Download and run rustup-init.exe (64-bit).

It will ask you some questions.

Download Visual Studio Community Edition Installer.

Open up Visual Studio Community Edition Installer and install the C++ core desktop features.

Verify Installation

From MacOS terminal or Windows CMD or PowerShell

rustc --version    # Should show Rust compiler version
cargo --version    # Should show Cargo package manager version
rustup --version   # Should show Rustup toolchain installer version

Troubleshooting Installation:

# Update Rust if already installed
rustup update

# Check which toolchain is active
rustup show

# Reinstall if needed (a last resort!!)
rustup self uninstall
# Then reinstall following installation steps above

Write and compile simple Rust program

Generally you would create a project directory for all your projects and then a subdirectory for each project.

Follow along now if you have Rust installed, or try at your first opportunity later.

$ mkdir ~/projects
$ cd ~/projects
$ mkdir hello_world
$ cd hello_world

All Rust source files have the extension .rs.

Create and edit a file called main.rs.

For example with the nano editor on MacOS

# From MacoS terminal
nano main.rs

or notepad on Windows

# From Windows CMD or PowerShell
notepad main.rs

and add the following code:

fn main() {
    println!("Hello, world!");
}

Note: Since our course notes are in mdbook, code cells like above can be executed right from the notes!

In many cases we make the code cell editable right on the web page!

If you created that file on the command line, then you compile and run the program with the following commands:

$ rustc main.rs    # compile with rustc which creates an executable

If it compiled correctly, you should have a new file in your directory

For example on MacOS or Linux you might see:

hello_world  % ls -l
total 880
-rwxr-xr-x  1 tgardos  staff  446280 Sep 10 21:03 main
-rw-r--r--  1 tgardos  staff      45 Sep 10 21:02 main.rs

Question: What is the new file? What do you observe about the file properties?

On Windows you'll see main.exe.

$ ./main           # run the executable
Hello, world!

Compiled (e.g. Rust) vs. Interpreted (e.g. Python)

Python: One Step (Interpreted)

python hello.py
  • Python reads your code line by line and executes it immediately
  • No separate compilation step needed

Rust: Two Steps (Compiled)

# Step 1: Compile (translate to machine code)
rustc hello.rs 

# Step 2: Run the executable
./hello
  • rustc is your compiler
  • rustc translates your entire program to machine code
  • Then you run the executable (why ./?)

The main() function

fn main() { ... }

is how you define a function in Rust.

The function name main is reserved and is the entry point of the program.

The println!() Macro

Let's look at the single line of code in the main function:

    println!("Hello, world!");

Rust convention is to indent with 4 spaces -- never use tabs!!

  • println! is a macro which is indicated by the ! suffix.
  • Macros are functions that are expanded at compile time.
  • The string "Hello, world!" is passed as an argument to the macro.

The line ends with a ; which is the end of the statement.

More Printing Tricks

Let's look at a program that prints in a bunch of different ways.

// A bunch of the output routines
fn main() {
    let x = 9;
    let y = 16;
    
    print!("Hello, DS210!\n");       // Need to include the newline character
    println!("Hello, DS210!\n");     // The newline character here is redundant

    println!("{} plus {} is {}", x, y, x+y);  // print with formatting placeholders
    //println!("{x} plus {y} is {x+y}");      // error: cannot use `x+y` in a format string
    println!("{x} plus {y} is {}\n", x+y);      // but you can put variable names in the format string
}

More on println!

  • first parameter is a format string
  • {} are replaced by the following parameters

print! is similar to println! but does not add a newline at the end.

To dig deeper on formatting strings:

Input Routines

Here's a fancier program. You don't have to worry about the details, but paste it into a file name.rs, run rustc name.rs and then ./name.

// And some input routines
// So this is for demo purposes
use std::io;
use std::io::Write;

fn main() {
    let mut user_input = String::new();
    print!("What's your name? ");
    io::stdout().flush().expect("Error flushing");  // flush the output and print error if it fails
    let _ =io::stdin().read_line(&mut user_input);  // read the input and store it in user_input
    println!("Hello, {}!", user_input.trim());
}

Project manager: cargo

Rust comes with a very helpful project and package manager: cargo

  • create a project: cargo new PROJECT-NAME

  • main file will be PROJECT-NAME/src/main.rs

  • to run: cargo run

  • to just build: cargo build

Cargo example

~ % cd ~/projects 

projects % cargo new cargo-hello
    Creating binary (application) `cargo-hello` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

projects % cd cargo-hello 

cargo-hello % tree
.
├── Cargo.toml
└── src
    └── main.rs

2 directories, 2 files

cargo-hello % cargo run
   Compiling cargo-hello v0.1.0 (/Users/tgardos/projects/cargo-hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/cargo-hello`
Hello, world!

% tree -L 3
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    ├── CACHEDIR.TAG
    └── debug
        ├── build
        ├── cargo-hello
        ├── cargo-hello.d
        ├── deps
        ├── examples
        └── incremental

8 directories, 6 files

Cargo --release

By default, cargo makes a slower debug build that has extra debugging information.

We'll see more about that later.

Add --release to create a "fully optimized" version:

  • longer compilation
  • faster execution
  • some runtime checks not included (e.g., integer overflow)
  • debuging information not included
  • the executable in a different folder
cargo-hello (master) % cargo build --release
  Compiling cargo-hello v0.1.0 (/Users/tgardos/projects/cargo-hello)
   Finished `release` profile [optimized] target(s) in 0.38s
(.venv) √ cargo-hello (master) % tree -L 2
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
   ├── CACHEDIR.TAG
   ├── debug
   └── release

5 directories, 4 files

Cargo check

If you just want to check if your current version compiles: cargo check

  • Much faster for big projects

Hello Rust Activity

  • Get in groups of 3+

  • Place the lines of code in order in two parts on the page: your shell, and your code file main.rs to make a reasonable sequence and functional code.

  • We'll take the last 5 minutes to share solutions

  • Don't stress if you didn't finish! Just paste what you have into GradeScope.

println!("Good work! Average: {:.1}", average);

cargo run

scores.push(88);

git push -u origin main

let average = total as f64 / scores.len() as f64;

cargo new hello_world

} else if average >= 80.0 {

nano src/main.rs

let total: i32 = scores.iter().sum();

if average >= 90.0 {

touch README.md

cd hello_world

fn main() {

git add src/main.rs

println!("Keep trying! Average: {:.1}", average);

let mut scores = vec![85, 92, 78, 96];

ls -la

echo "This is a grade average calculator" > README.md

} else {

git commit -m "Add calculator functionality"

}}

println!("Excellent! Average: {:.1}", average);