This is the original place I made these notes.

I'm moving them over to rust-notes.alanwsmith.com

Scratchpad 2

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

Scratchpad

SOURCE CODE

CODE RUNNER

Step By Step

Step By Step Test

This is the first prototype of the viewer. Next step is to highlight lines that changed.


NOTE: These are in reverse order of how they were created so I don't have to keep scrolling down

TODO: Come up with the list of things you can do for references:

immutable - immutable mutable - immutable mutable - mutable

and that you can't do

immutable - mutable

Immutable -> Immutable

fn main() {
  let alfa = String::from("apple");
  widget(&alfa);
  println!("alfa is {alfa}")
}

fn widget(value: &String) {
  println!("widget got {value}")
}

Mutable -> Mutable

fn main() {
  let mut alfa = String::from("apple");
  widget(&mut alfa);
  println!("alfa is {alfa}");
}

fn widget(value: &mut String) {
  value.push_str("-pie")
}

Mutable -> Immutable

fn main() {
  let mut alfa = String::from("apple");
  widget(&alfa);
  println!("alfa is {alfa}")
}

fn widget(value: &String) {
  println!("widget got {value}")
}

Immutable -> Mutable (will crash)

fn main() {
  let alfa = String::from("apple");
  widget(&mut alfa);
}

fn widget(value: &mut String) {
  println!("widget got {value}")
}

This crashes as expected because it's not a reference

fn main() {
  let alfa = String::from("apple");
  widget(alfa);
  println!("alfa is {alfa}")
}

fn widget(value: String) {
  println!("widget got {value}")
}

Other situaltions:

combinging mutalbe and immutable references.

Hello, World - Your Turn

I learn best by doing. For programming that means typing code examples into an editor. The second feature of the site is designed to do just that. You'll find Source Code blocks paired with Code Runners editors across the site. They let you enter and run the code without having to install Rust yourself.

Typing slows me down in beneficial ways. I focus more on the the code. That helps me understand and remember how things work. The approach can take some getting used to. Stick with it for a bit to see if you start to see the benefit.

Here's the first one. It's the same "Hello, World" as the example on the prior page. Type it in and run it to see the output.

SOURCE CODE

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

CODE RUNNER

The Status Line

Learning to read error message is a crital part of programming. We'll cover them. But, hitting one because you made a typo in a tutorail is a waste of time, energy, and motivation.

You may have noticed the "Status" line in the Code Runner on the previous pages. It's designed to eliminate typos by highlighting them as soon as they occur and show you what character to use.

If you've ever entered a huge block of tutorial code only to be hit with an indecipherable error because of an unrelated typo, you know why I built it.

Quick Notes

A few quick notes before we continue:

  • This site is a work in progress. It's built from my actual, in-progress notes. It only goes as far as I've made it learning the language.

  • The content is designed for folks who have a little programming experience. If you know what variables, loops, conditionals, and functions are you'll be fine.

  • Examples are intentionally sort. That often means doing things that would be silly in useful programs. For example, defining a variable that's used only once on the following line. The goal is to show how to use something not when to use it.

  • The Code Runners have a "Disable Status Line" button. Hit that to prevent the status line from warning you about typos if you want to change code to play with the code samples.

Variables

Basic Rust variables are created using this formula:

  1. The let keyword
  2. A name
  3. The = sign
  4. The value
  5. A ; character

The values for a string of text looks like this: String::from("apple"). Using that and alfa for the name we can create a variable like this:

let alfa = String::from("apple");

In Rust, setting variables is called "binding". So, the above line binds a String with the text "apple" to the variable alfa.

Printing Variables

Most examples on the site print something. We'll do that using println!().

println!() uses what are called "format strings" as templates for output. Putting the name of a variable inside {} curly brackets in a format string outputs its value. For example, if we have a variable named alfa we can print it like this:

println!("alfa is {alfa}");

A full program looks like this:

fn main() {
  let alfa = String::from("apple");
  println!("alfa is {alfa}");
}

which outputs:

alfa is apple

Step By Step

Printing Variables - Your Turn

Here's the same code to try yourself.

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  println!("alfa is {alfa}");
}

CODE RUNNER

Multiple Variables

Multiple variables can be printing in the same println!() expression by adding more {} curly brackets with the desired variable names in them.

For example, here's a program that creates three variables and prints them:

fn main() {
  let alfa = String::from("apple");
  let bravo = String::from("berry");
  let charlie = String::from("cherry");

  println!("{alfa} {bravo} {charlie}");
}

The output is:

apple berry cherry

Step By Step

Alternate Style

There will be times when we need to use an alternate syntax for printing. Instead of this:

println!("alfa is {alfa}");

the alternate style uses an empty set of {} curly brackets inside the quotes with the variable name outside them and separated by a comma like this:

println!("alfa is {}", alfa);

A full program looks like this:

fn main() {
  let alfa = String::from("apple");
  println!("alfa is {}", alfa);
}

which outputs:

alfa is apple

TODO

  • add note about why that change is necessary

Multiple Variables - Alternate Syntax

The alternate syntax uses multiple empty {} curly bracket placeholders and the variables outside the quotes as before. The first variable goes into the first {}. The second variable goes into the second {} and so on.

It looks like this:

fn main() {
  let alfa = String::from("apple");
  let bravo = String::from("berry");
  let charlie = String::from("cherry");

  println!("{} {} {}", alfa, bravo, charlie);
}

which produces the same output:

apple berry cherry

Mutable Variables

Rust variables are immutalbe by default. That means you can't change them after they've been set. The mut keyword makes them mutable allowing them to be changed after they are created.

Here's an example of creating a default (immutable) variable:

let alfa = String::from("apple");

And this is how to create one using the mut keyword to make it mutable:

let mut alfa = String::from("apple");

.push_str()

One way to change a mutable String is with .push_str(). It adds content onto the end of an existing string. For example, this adds "pie" to the end of the "apple" String

let mut alfa = String::from("apple");
alfa.push_str("pie");

Step By Step

Mutable Variables - Your Turn

SOURCE CODE

fn main() {
  let mut alfa = String::from("apple");
  alfa.push_str("pie");
  println!("alfa is {alfa}");
}

CODE RUNNER

Variables Names With _ Underscores

When the Rust compiler is checking a program to make sure it's valid it also checks for unused variables. TODO: Show an example where the warning for an unused variable shows up.

Adding an _ underscore as the first character of the variable name tells the compiler that we're expecting the variable to be unused so it shoudn't output the warning. This is helpful during the development process to avoid warnings when code isn't fully complete.

We'll be using the underscore in some of the examples to keep the code short without causing the warning.

Functions

Each program we make will have a main() funciton. It's the first things that gets executed.

We can create other functions as well. We'll use three types of functions with the following properties:

  1. Does not accept arguments - Has no return value
  2. Accepts arguments - Has no return value
  3. Does not accept arguments - Does have a return value
  4. Accepts arguments - Does have a return value

The formulas for each of the three types are on the next three pages. Don't worry too much about memorizing them. We'll use each plenty of times.

Functions With No Arguments And No Return Value

The formula for functions that take no arguments and return no values is:

  1. The fn keyword
  2. A name
  3. An empty set of () parenthesis
  4. A set of {} curly brackets surrounding the function's code block

For example:

fn widget() {
  println!("this is widget");
}

Calling functions without arguments is done using its name followed by empty (), like:

widget();

Step By Step

Your Turn

SOURCE CODE

fn main() {
  widget();
}

fn widget() {
  println!("this is widget");
}

CODE RUNNER

Functions That Take Arguments But Have No Return Value

The formula for functions that do take arguments without returning a value is must the same as functions that don't. The difference is that the () parens are populated to let the function know about the incoming data

  1. The fn keyword
  2. A name
  3. Parenthesis with argument details. For example: (thing: String)
  4. A set of {} curly brackets surrounding the function's code block

Step By Step

Your Turn

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  widget(alfa);
}

fn widget(thing: String) {
  println!("widget got {thing}");
}

CODE RUNNER

Takes No Arguments But Has A Return Value

In addition to receiving data, functions can also return values back to the code that called them. Difining those functions is done by adding -> along what will be send back behindg the parenthesis after the name. For example

widget() -> String

The parts of the function look like this:

  1. The fn keyword
  2. A name
  3. An empty set of () parenthesis.
  4. The -> symbol followed by what type of data will be returned
  5. A set of {} curly brackets surrounding the function's code block

Step By Step

Your Turn

SOURCE CODE

fn main() {
  let alfa = widget();
  println!("alfa got {alfa}");
}

fn widget() -> String {
  let bravo = String::from("berry");
  bravo
}

CODE RUNNER

Functions That Take Arguments And Have Return Values

Step By Step

Your Turn

Ownership

NOTE: This is my understanding of how ownership based on The Rust Book. I need to have it vetted by someone who knows more about Rust.


A fundamental feature of Rust is that every value has an "owner". When we bind a value to a variable like this:

let alfa = String::from("apple");

that variable becomes the owner of the value. In the case that means alfa now owns the String of "apple". You can think of an "owner" like a wrapper around the value.

As long as a varible is owner of a value it can use it. For example, if we call this after setting alfa like we did above:

println!("alfa is {alfa}");

it will output:

alfa is apple

becuase alfa owns the String made from "apple".

Step 1

Let's go through the process of creating a variable and binding a value to it to get a better understading of ownership. We'll start off by splitting this line in half:

let alfa = String::from("apple");

The let alfa on the left side of our equal sign is what's responsible for creating the variable with our specified name.

Ownership 1

Adding in the String::from("apple") to complete the expression creates the value, binds it to alfa, and makes alfa its owner in the process.

Ownership 2

Step 2

When we want to use the value of the String we don't access it directly. We get it from through the variable.

For exaple, if we do:

println!("alfa has {alfa}");

Rust sees the request for alfa and accesses the variable to get the value it's bound to and returns it for the output:

alfa is apple

Here's what that looks like:

Ownership 3

Step 3

Now let's make a new variable called bravo and bind our exsiting alfa variable to it.

As before, the let bravo on the left side of the eaqual sign is what's responsible for making the variable

Ownership 4

What Doesn't Happen

Next we'll complete our expression with this:

let bravo = alfa;

Becasue alfa became the owner of our String value when we did this:

let alfa = String::from("apple");

It's natural to think bravo would become the owner of alfa when we completed our expression with let bravo = alfa;

If that were the case, we'd get something that looks like this:

Ownership 5

We would then be able to get at our String value from both alfa and bravo. (alfa would be direct and bravo would go through alfa).

But, that's not what happens.

Step 4

TODO: Can variables have owners? And does this page use the right language?


Let's look at what does happen starting with a review of Step 3 where:

  1. The alfa variable has been created
  2. The String::from("apple") value has been created and bound to alfa making the variable its owner
  3. We've made the first half of the expression to bind alfa to bravo

Our illustration looked like this:

Ownership 4

Now we can complete the expression with:

let bravo = alfa;

That assignment doesn't make bravo the owner of alfa. Instead the value that's insdie alfa gets moved into bravo and bravo becomes the owner.

Ownership 6

Step 5

Using bravo gives us the String value output of "apple" the same way alfa did before the move.

Ownership 7

If we combine and run all the code we've use so far everything works.

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  println!("alfa has {alfa}");

  let bravo = alfa;
  println!("bravo has {bravo}");
}

CODE RUNNER

Step By Step

Your Turn

Hitting A Moved Error

Where things go wrong is if we try to use alfa again. Values can only have one owner. When we did let bravo = alfa; it transferred ownership of the String from alfa to bravo.

Without ownership, alfa can't work with the String any more`

Trying to use it looks like this:

Ownership 8

Here's the full example of the code where we try to access alfa with this line

println!("alfa has {alfa}");

Since the String was moved to bravo we get an error that we'll discuss next.

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  let bravo = alfa;

  println!("alfa has {alfa}");
}

CODE RUNNER

Your Turn

Moving Error Details

TKTKTKT - Write up the details of this error.

This is the error.

   Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `alfa`
 --> src/main.rs:8:23
  |
2 |   let alfa = String::from("apple");
  |       ---- move occurs because `alfa` has type `String`, which does not implement the `Copy` trait
...
5 |   let bravo = alfa;
  |               ---- value moved here
...
8 |   println!("alfa has {alfa}");
  |                       ^^^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
5 |   let bravo = alfa.clone();
  |                   ++++++++

For more information about this error, try `rustc --explain E0382`.
error: could not compile `playground` due to previous error

References

References offer a way to access a value from multiple variables without changing ownership. They're made by putting the & symbol in front of the original variable that holds the value.

For example this code moves ownership of the String on the bravo = alfa line.

  let alfa  = String::from("apple");
  let bravo = alfa;

If we tried to access alfa after doing that we'd get the borrow of moved value error.

Adding the & like this means the value isn't moved to bravo and that alfa retains ownership.

  let alfa  = String::from("apple");
  let bravo = &alfa;

We can now use the value from both variables.

Setp By Step

Example

Here's an example using a reference that outputs:

alfa is apple
bravo is apple

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  let bravo = &alfa;

  println!("alfa is {alfa}");
  println!("bravo is {bravo}");
}

CODE RUNNER

Step 1

The first step is to create an initial variable with the standard line:

let alfa = String::from("apple");

We'll use a slightly different illustration this time where the String value has as side exposed.

References Illustration

Step 2

Now we can create our reference with the & character like this:

let bravo = &alfa;

We'll represent the reference this way where alfa still owns the String but bravo is connected to it as well.

References Illustration

Working Example

Here's a refined version of our previous example that didn't work:

fn main() {
  let alfa = String::from("apple");
  let bravo = alfa;

  println!("alfa has {alfa}");
  println!("bravo has {bravo}");
}

We can get the code working with a reference by changing the line

let bravo = alfa;

to:

let bravo = &alfa;

Here's the full sample which outputs:

alfa has apple
bravo has apple

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  let bravo = &alfa;

  println!("alfa has {alfa}");
  println!("bravo has {bravo}");
}

CODE RUNNER

Multiple References

Variables are immutalbe by default. So are references. One benefit of that since we can be sure they won't change we can add as many references as we like.

Here's an illustration using two new variables charlie and delta that reference the original alfa value.

References Illustration

And here's the code showing it working. It outputs:

alfa has apple
bravo has apple
charlie has apple
delta has apple

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  let bravo = &alfa;
  let charlie = &alfa;
  let delta = &alfa;

  println!("alfa has {alfa}");
  println!("bravo has {bravo}");
  println!("charlie has {charlie}");
  println!("delta has {delta}");
}

CODE RUNNER

Step By Step

Your Turn

Cheatsheet

NOTE: These are draft notes.

TODO: Figure out where to put this in the book.


Here's the high level:

Create An Immutable Variable

let alfa = String::from("apple");

Create A Mutable Variable

let mut alfa = String::from("apple");

Create A Struct

struct Widget {
  alfa: String,
  bravo: String
}

Create A Function With No Arguments Or Return Value

fn widget() {
  println!("this is widget");
}

Create A Function That Has One Argument Reference But No Return Value

fn widget(thing: &String) {
  println!("widget to {}", thing);
}

Create A Function With Two Arguments But No Return Value

fn widget(thing1: &String, thing2: &String) {
  println!(
    "widget got {} and {}",
    thing1,
    thing2
  );
}

Create A Function With A Return Value But No Arguments

fn widget() -> String {
  let alfa = String::from("apple");
  alfa
}

Create A Function With A Return Value And An Argument Reference

fn widget(thing: &String) -> String {
  println!("widget got {}", thing);
  let alfa = String::from("apple");
  alfa
}

Create A Function That Takes A Mutable Reference Argument

fn widget(thing: &mut String) {
  thing.push_str("additional characters")
}

Drafts

All the pages that follow are rough drafts and sometimes little more than collections of notes. Feel free to look around keeping that in mind.

Errors: Result and panic!()

There are two categories of errors in Rust programs and expressions the correspond with them:

  1. Errors the program can't recover from which are triggered by panic!()
  2. Errors the program can recover from which is tied to a Result

These are draft notes


TODO: Switch over to using ENV Vars instead of the file system.

fn main() {
  panic!("goes boom");
}
fn main() {
  let alfa = vec![
    3, 5, 7
  ];

  alfa[100];
}

Errors - Result

From the book:

"in production code, most Rustaceans choose expect rather than unwrap to give more context about what happens.

Result<T, E>

is defined as

enum Result<T, E> {
  Ok(T),
  Err(E)
}

This panics

use std::fs::File;

fn main() {
  let the_file_result = File::open("hello.txt");

  let the_file = match the_file_result {
    Ok(file) => file,
    Err(error) => panic!("{:?}", error)
  };

  println!("Got {:?}", the_file)
}

Instead of the match, you can use .unwrap(). This throws a panic if it hits the error. That doesn't have to be the case, you can do somethiing else as long as it returns a proper value (TBD on how to do that)

use std::fs::File;

fn main() {
  let _the_file = File::open("hello.txt")
    .unwrap_or_else(|error| {
      panic!("Error {:?}", error)
    });
}

This will panic because there is no _or_else.


use std::fs::File;

fn main() {
  let _the_file = File::open("hello.txt").unwrap();
}

Using .expect() let you define an error message. So the value gets set and then will get an error with the message if something goes wrong. (TODO: Figure out what the difference is between the value returned. i.e. do .unwrap() and .expect() set the same value.

(Note, this didn't give the .expect() error message, need to look more into that)

use std::fs::File;

fn main() {
  let _the_file = File::open("hello.txt")
    .expect("The error happened");
}

From the book:

// I think this actualy makes a new file // on the playground so it doesn't panic.

use std::fs::File;
use std::io::ErrorKind;

fn main() {
  let greeting_file_result = File::open("hello.txt");

  let greeting_file = match greeting_file_result {
    Ok(file) => file,
    Err(error) => match error.kind() {
      ErrorKind::NotFound => match File::create("hello.txt") {
        Ok(fc) => fc,
        Err(e) => panic!("Could not create file {:?}", e)
      },
      other_error => {
        panic!("Could not open file {:?}", other_error)
      }
    }
  };
}

Propigating Errors

This is the first example they put in but they say they'll do a different way next. (i.e. this is the manual way they are using to show some details on how things work. (no main so not sure it if compiles)

From the book

#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Results<String, io:Error> {
  let username_file_result = File::open("hello.txt");

  let mut username_file = match username_file_result {
    Ok(file) => file,
    Err(e) => return Err(e)
  };

  let mut username = String::new();

  match username_file.read_to_string(&mut username) {
    Ok(_) => Ok(username),
    Err(e) => Err(e),
  }
}
}

Here's the shorter version:

#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Resutls<String, io:Error> {
  let mut username_file = File::open("users.txt")?;
  let mut username = String::new();
  username_file.read_to_string(&mut username)?;
  Ok(username)
}
}

And even shorter. This is a better way to show things. Should be able to explain it directly.

NOTE: Talks about the "from" casting that happens via the ?

#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Results<String, io::Error> {
  let mut username = String::new();

  File::open("users.txt")?.read_to_string(&mut username)?;

  Ok(username)
}
}

And then finally for the actual use case if you need to read something in:

#![allow(unused)]
fn main() {
use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
  fs.read_to_string("users.txt");
}
}

The ? does an early return.

The ? can only be used for functions whose return type is compatable. For example, you can't use one in main because there is no Result return type.

You can use an Option, Result, or something that implements FromResidaul


From the book:

The error message also mentioned that ? can be used with Option values as well. As with using ? on Result, you can only use ? on Option in a function that returns an Option. The behavior of the ? operator when called on an Option is similar to its behavior when called on a Result<T, E>: if the value is None, the None will be returned early from the function at that point. If the value is Some, the value inside the Some is the resulting value of the expression and the function continues. Listing 9-11 has an example of a function that finds the last character of the first line in the given text:

#![allow(unused)]
fn main() {
fn last_character_of_first_line(text: &str) -> Option<char> {
  text.lines().next()?.chars().last()
}
}

This function returns Option because it’s possible that there is a character there, but it’s also possible that there isn’t. This code takes the text string slice argument and calls the lines method on it, which returns an iterator over the lines in the string. Because this function wants to examine the first line, it calls next on the iterator to get the first value from the iterator. If text is the empty string, this call to next will return None, in which case we use ? to stop and return None from last_char_of_first_line. If text is not the empty string, next will return a Some value containing a string slice of the first line in text.


It is possible to return a Result<(), Box<dyn Error>> from main()

TODO: Look at Box<dyn Error>

If main returns a Result with a value of 0 that means things went well. Any other value indicates a problem



Trying to use env vars.

.is_ok() returns true if a value is OK() and false if it's not.

This just checks to see if an envvar exists and sets alfa as true or false

use std::env;

fn main() {
  let alfa = env::var("FORCE_ERROR").is_ok();
  println!("alfa is {alfa}");
}
use std::env;

fn main() {
  let alfa = String::from("test_var");
  let bravo = check_var(&alfa);
  println!("bravo is {bravo}");
}

fn check_var(key: &String) -> String {
  match env::var(key) {
    Ok(value) => value,
    Err(e) => String::from("no_value")
  }
}

use std::env;

fn main() {
  let alfa = String::from("test_var");
  let bravo = check_var(&alfa);
  println!("bravo is {bravo}");
}

fn check_var(key: &String) -> String {
  env::var(key).expect("no var set. panicing")
}

panic!() Errors

A panic!() happens when the program tries to do something it's not capable of and doesn't have a mechanism to recover from.

For example, manually trying to access an index location of a Vec that doesn't exist causes a panic.

So when you try to do this:

fn main() {
  let alfa = vec![
    String::from("apple"),
    String::from("berry")
  ];

  let bravo = &alfa[100];
  println!("bravo is {bravo}");
}

The program panics and dumps this to the output:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.44s
     Running `target/debug/playground`
thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 100', src/main.rs:7:16
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

These errors are different than the ones weve been getting so far. Our earlier programs weren't able to compile at all so there was no danger of shipping the program because the compiler wouldn't allow it to be built. With panic!() errors, the program is able to complie and make the acutal program file. So, it's possible to ship a program with a panic!() bug in it.

Identifying panic!() Errors

There are two quick ways to tell the difference in the error messages. To see them let's look at the output of a program that tries. to change an immutalbe value:

fn main() {
  let alfa = String::from("apple");
  println!("alfa is {alfa}");
  alfa = String::from("berry");
  println!("alfa is {alfa}");
}

That output looks like this (which will refer to as the compiling error)

   Compiling playground v0.0.1 (/playground)
error[E0384]: cannot assign twice to immutable variable `alfa`
 --> src/main.rs:4:3
  |
2 |   let alfa = String::from("apple");
  |       ----
  |       |
  |       first assignment to `alfa`
  |       help: consider making this binding mutable: `mut alfa`
3 |   println!("alfa is {alfa}");
4 |   alfa = String::from("berry");
  |   ^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `playground` due to previous error

And here's another copy of our panic!() error from the the previous page (which we'll refer to as the panic error)

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.44s
     Running `target/debug/playground`
thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 100', src/main.rs:7:16
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The first way to tell the difference between the compiling and panic errors is with the first three lines. Both errors start of with Compiling.... Then the compiling error indicates an with an error number on the next line (e.g. error[E0384]) while the panic error shows Finished... on line two and Running... on the third line. Those two lines indicate that the compiler was able to do it's job and start to run the program.

The second way to tell the difference between the two types of errors is at the end of teh error message. The compiler error ends with error: could not compile... identifies the core problem we're discussing. For the panic error, the next to last line is thread 'main' panicked at... with more text desciging exactly where the panic occurred.

Causing panic!() Errors

panic!() errors can be caused by tring to do something the program can't handle like the way we tried to access an index larger than the available values in a Vec. We can also trigger them directly by using the panic!() expression.

For example, this code will compile and run:

fn main() {
  let alfa = String::from("apple");
  println!("alfa is {alfa}");
}

Adding a panic line causes a crash:

fn main() {
  let _alfa = String::from("apple");
  panic!("break here");
}

Result Type For Error Handling

The Result type is used to return values. It can contain either and Ok or an Err. The basic signature is:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

The T stands for type, and E stands for error. Those are generic types which means that we can put any type in the slot. The first T and E in Result<T, E> setup the result of enum to be able to use them (TODO: write up generic types before this)

The Ok(T) turns into whatever type the process that made it needs to send.

For example, the code below is used to get the string value from an Environmental Variable. But, the string doesn't come in directly. Instead the env::var returns a Result that contains either Ok(String) or Err(VarError).

So, to get to the string we have to examine the Result with match to see if it contains Ok or Err

Here's an example

#![allow(unused)]
fn main() {
use std::env;

let key = "HOMEx";
let returnValueAsResult = env::var(key);

match returnValueAsResult {
    Ok(value) => {
      println!("{} is {}", key, value);
    }
    Err(error) => {
      println!("{} - {}", key, error);
    }
}
}
#![allow(unused)]
fn main() {
use std::env;

let key = "NON_EXISTANT_KEY";
match env::var(key) {
    Ok(val) => println!("{key}: {val:?}"),
    Err(e) => println!("couldn't interpret {key}: {e}"),
}
}

Step By Step

Result And Option

TODO: Make a page showing both Result and Option and doing some comprison of the two

Function Argument References

References are required to send the value of a variable to a function and still be able to use the original variable afterwards.

That looks like this:

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  widget(&alfa);
  println!("alfa is {alfa}");
}

fn widget(thing: &String) {
  println!("widget got {thing}");
}

CODE RUNNER

With Variables

We can also create a reference in a new variable the send it to the function. Since we'll define the new varaible as a refernce like this:

let bravo = &alfa

We don't need to send it to use & to make it a reference again when we send it to the widget() function. That is, instead of this:

widget(&bravo);

We do this:

widget(bravo);

Here'e the code:

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  let bravo = &alfa;

  widget(bravo);

  println!("alfa is {alfa}");
  println!("bravo is {bravo}");
}

fn widget(thing: &String) {
  println!("widget got {thing}");
}

CODE RUNNER

Error Example

Here's the same thing, but without & which shows the error.

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  println!("call widget next");

  widget(alfa);
  println!("alfa is {alfa}");
}

fn widget(value: String) {
  println!("widget got {value}");
}

CODE RUNNER

Mutable Function Argument References

TKTKTKT

TODO: Explain this line by line

SOURCE CODE

fn main() {
  let mut alfa = String::from("apple");
  widget(&mut alfa);
  println!("alfa is {alfa}");
}

fn widget(value: &mut String) {
  value.push_str("-pie");
}

CODE RUNNER

Structs

Structs allow you to group data together. They're defined with the struct keyword followed by the name to use then {} curly brackets that contain the details. The content inside the curly brackets defined what are known as "fields" and the type of data they can hold.

Here's the definition for a struct named Widget that contains two fields alfa and bravo. Each one of the fields is setup to hold a String

struct Widget {
  alfa: String,
  bravo: String
}

Using a struct is done by assigning it to a variable. Doing so is called making an instance. That's done like this:

let thing = Widget {
  alfa: String::from("apple"),
  bravo: String::from("bettey")
}

Accessing the values inside an instance of a struct is does using the varialbe name plus a dot followed by the name of the field. For example:

println!("{thing.alfa}");

Structs - Step By Step

TODO: Make sure to note that the let expression needs a ; at the end of it

Step By Step

Your Turn

SOURCE CODE

struct Widget {
  alfa: String
}

fn main() {
  let thing = Widget {
    alfa: String::from("apple")
  };
  println!("{}", thing.alfa);
}

CODE RUNNER

Mutalbe Struct Instances

Struct instances are immutalbe by default. They can be made mutable with the same mut keyword we've used before. For example, instead of

let thing = Widget {
  alfa: String::from("apple")
}

We'd use this

    ↓↓↓
let mut thing = Widget {
  alfa: String::from("apple")
}

Mutalbe Struct Instances - Step By Step

Step By Step

Your Turn

Passing Structs To Functions

Step By Setp

Passing Structs To Functions

SOURCE CODE

struct Widget {
  color: String
}

fn main() {
  let alfa = Widget {
    color: String::from("red")
  };
  report_on(&alfa);
}

fn report_on(thing: &Widget) {
  println!("Color {}", thing.color);
}

CODE RUNNER

Passing Mutalbe Structs To Functions

Step By Step

Passing Mutable Structs To Functions

SOURCE CODE

struct Widget {
  color: String
}

fn main() {
  let mut alfa = Widget {
    color: String::from("red")
  };
  update(&mut alfa);
  println!("Color {}", alfa.color)
}

fn update(thing: &mut Widget) {
  thing.color = String::from("green")
}

CODE RUNNER

Option Type

fn main() {
  let mut alfa = vec![
    String::from("apple"),
  ];

  let bravo = alfa.pop();
  let item = alfa.pop();

  match item {
    Some(value) => {
      println!("got {value}");
    }
    None => {
      println!("got nothing");
    }
  }
}
fn main() {
  let mut alfa = vec![
    String::from("apple"),
  ];
  check_value(alfa.pop());
  check_value(alfa.pop());
}

fn check_value(value: Option<String>) {
  match value {
    Some(value) => {
      println!("got {}", value);
    }
    None => {
      println!("got nothing");
    }
  }
}


Example using a funciton

enum Widget {
  Alfa(String),
  Bravo
}

fn main() {
  let thing = Widget::Alfa(
    String::from("apple")
  );

  check_thing(thing);
}


fn check_thing(value: Widget) {
  match value {
    Widget::Alfa(x) => {
      println!("alfa has {}", x);
    }
    Widget::Bravo => {
      println!("bravo has nothing");
    }
  }
}

Option Type

#![allow(unused)]


fn main() {
}

Options are returned by lots of things to let you know if there is a valid value or not.

More stuff on this page:

https://doc.rust-lang.org/book/ch06-02-match.html

Your Turn

struct Widget {
  thing: Option<String>
}

fn main() {
  let alfa = Widget { 
    thing: Some(
      String::from("apple")
    )
  };

  match alfa.thing {
    Some(value) => {
      println!("thing is {}", value);

    }
    None => {
      println!("no value");
    }
  }

}
struct Widget {
  thing: Option<String>
}

fn main() {
  let bravo = Widget { 
    thing: None
  };

  match bravo.thing {
    Some(value) => {
      println!("thing is {}", value);

    }
    None => {
      println!("no value");
    }
  }

}

Struct Methods

Struct methods are like functions, but that are attached to structs. They are put inside an impl keyword code block that has the same name as the struct they are for. Here we are using an empty struct and adding a method that prints out "the quick fox". (Notice that the argument &self is being passed to the fn. That is always the case for methods. (TODO: talk more about that)

Note that methods are called with ().

struct Widget;

impl Widget {
  fn do_output(&self) {
    println!("the quick fox");
  }
}

fn main() {
  let thing = Widget;
  thing.do_output();
}

Using fields

This is done by using &self which is alwasy the first argument passed in

struct Widget {
  alfa: bool,
}

impl Widget {

  fn do_output(&self) {
    println!("alfa is {}", &self.alfa);
  }

}

fn main() {

  let thing = Widget {
    alfa: true
  };

  thing.do_output();

}

In the more advanced section, point out that &self is shorthand for self: &Self that Rust provides out of the box.


Talks about using methods instead of functions as some orginaztion and keeping stuff together.


Passing values to methods

struct Widget;

impl Widget {

  fn show_value(&self, value: i32) {
    println!("Value is {}", value);
  }

}

fn main() {

  let alfa = Widget;

  alfa.show_value(7);

}

Associated Functions

these are like methods, but don't have '&self'

Maybe push this to later in the book.

String::from() is an associated function.

They are often used to make a new instance of the thing (e.g. with Widget::new) by returning Self

The new is not special or a reserved keyword or otherwise build into the language.

NOTE: Things are normally writtin in the order: struct, impl, main but this works and is easier for me to following when I'm making learning.

The Self in the fn and return value are aliases to what's named in impl in this case Widget. You can replace Self with Widget and it'll still work.

fn main () {
  let thing = Widget::new(7);

  println!("thing.alfa {}", thing.alfa);
  println!("thing.bravo {}", thing.bravo);
}

impl Widget {
  fn new(load_value: i32) -> Self {
    Self {
      alfa: load_value,
      bravo: load_value,
    }
  }
}

struct Widget  {
  alfa: i32,
  bravo: i32,
}

Associated function also provide for name spcing. Here to stucts are given an associated function with the name my_value. Since both are called with their struct names think get namespaced and they don't conflict.

fn main() {
  Alfa::show_message();
  Bravo::show_message();
}

impl Alfa {
  fn show_message() {
    println!("alfa")
  }
}

impl Bravo {
  fn show_message() {
    println!("bravo")
  }
}

struct Alfa;
struct Bravo;

Multiple impl blocks

You can make multilpe impl blocks. The book says there's not a real reason to do that for the most part. There will be a later case with generics types and traits. But kick this out until then.

struct Widget;

impl Widget {
  fn alfa(&self) {
    println!("alfa");
  }
}

impl Widget {
  fn bravo(&self) {
    println!("bravo");
  }
}

fn main() {

  let thing = Widget;

  thing.alfa();
  thing.bravo();
}

Enums

Purpose of an email is to be one of a specific type and nothing else. (TODO: Show structs as enum types.)

TODO: Show how you have to cover all the options. And how you can do that with the catch all.

Show how the turn values from match must be the same if you are assinging it to a value.

Show a version where the stuff inside the emum value is a struct of some other type.

Your example:

enum Color {
  Red,
  Green,
  Blue
}

fn main() {
  let alfa = Color::Blue;

  match alfa {
    Color::Red => {
      println!("Red like fire");
    }
    Color::Green => {
      println!("Green like grass");
    }
    Color::Blue => {
      println!("Blue like the sky");
    }
  }
}

enum Color {
  Red(String),
  Green(String),
  Blue(String)
}

fn main() {
  let alfa = Color::Blue(String::from("navy"));

  match alfa {
    Color::Red(name) => {
      println!("{name} is red like fire");
    }
    Color::Green(name) => {
      println!("{name} is green like grass");
    }
    Color::Blue(name) => {
      println!("{name} is blue like the sky");
    }
  }
}
enum Color {
  Red(String),
  Green(String),
  Blue(String)
}

fn main() {
  let alfa = Color::Blue(String::from("navy"));

  let bravo = match alfa {
    Color::Red(name) => name,
    Color::Green(name) => name,
    Color::Blue(name) => format!("thing {name}")
  };
  println!("bravo is {bravo}");
}

enum Color {
  Red(String),
  Green(String),
}

fn main() {
  let alfa = Color::Red(String::from("apple"));

  match alfa {
    Color::Red(name) => runRed(&name),
    Color::Green(name) => runGreen(&name),
  };
}

fn runRed(value: &String) {
  println!("got {value}")
}

fn runGreen(value: &String) {
  println!("got {value}")
}
enum Color {
  Red(String),
  Green(String),
}

fn main() {
  let alfa = Color::Red(String::from("apple"));

  let bravo = match alfa {
    Color::Red(name) => runRed(&name),
    Color::Green(name) => runGreen(&name),
  };

  println!("bravo is {bravo}")
}

fn runRed(value: &String) -> String {
  format!("a {}", value)
}

fn runGreen(value: &String) -> String {
  format!("a {}", value)
}

Original Notes


NOTE: It's possibe that you don't want to show code until you show the if let or the match stuff for how to get something out.


Enums let you define something as one out of a possible set of values.

Anything that's valid as a stuct is also valid as an enum.

these are the base examples from the book, which I don't really understand becuase they don't really show how to use them.

enum IpAddrKind {
  V6,
  V4
}

fn main() {
  let four = IpAddrKind::V4;
  println!("here");
}

That sets the type to ipAddrKind overall. This lets us do:

fn route(ip_kind, IpAddrKind) {}

The book goes on to show this

enum IpAddr {
  V4(String),
  V6(String)
}

fn main() {

  let home = IpAddr::V4(String::from("127.0.0.1"));
  let loopback = IpAddr::V4(String::from("::1"));

}

TODO: Add impl function being assocaited with an enum. This is based off this: https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html Need to check and make sure this is a standard way to do stuff (becuse there wasn't a full example in the book.)

NOTE: This is not a good example.

enum Widget {
  Alfa,
  Bravo,
}

impl Widget {
  fn show_value(&self) {
    println!("Widget");
  }
}

fn main() {

  let thing1 = Widget::Alfa;
  let thing2 = Widget::Bravo;

  thing1.show_value();
  thing2.show_value();

}

This is an attempt to make an example that's easier to understand what's going on.

#[derive(Debug)]
enum Widget {
  Alfa { value: i32 },
  Bravo { value: f32}
}

fn main() {
  let thing1 = Widget::Alfa {value: 7};
  let thing2 = Widget::Bravo {value: 3.4};

  println!("here {:?}", thing1.value);
}

Vecs

vec stands for Vector. The basic details of a vec are:

  • They hold a collection of values (like an array)
  • All the values they hold must the same type (e.g. String)
  • It's possible to add and remove items from the vec

Creating a vec can be done with with vec![]. For example:

let alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

Reading values in a vec can be done with using the zero based index/offest of the item to retrieve.

let bravo = &alfa[2]

The & in &alfa[2] means that we get a reference to the value so that ownership isn't transferred.

Full example:

fn main() {
  let alfa = vec![
    String::from("apple"),
    String::from("berry"),
    String::from("cherry")
  ];

  let bravo = &alfa[2];
  println!("bravo is {bravo}");
}

If the vec is mutable, adding more elements can be done with .push() like this:

alfa.push(String::from("date");
fn main() {
  let mut alfa = vec![
    String::from("apple"),
    String::from("berry"),
    String::from("cherry")
  ];

  alfa.push(String::from("date"));

  let bravo = &alfa[3];
  println!("bravo is {bravo}");
}

TODO: Cover .get() which returns an Option<&T> once your figure out how those work.

This is the example from The Rust Book

#![allow(unused)]
fn main() {
let alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

let bravo = alfa.get(2);

match bravo {
  Some(bravo) => {
    println!("bravo is {}", bravo);
  }
  None => {
    println!("No value at index 2");
  }
}
}

Here's the same thing but using .get() to request index 3 which doesn't exist. The program doesn't crash. It shows the None arm of the match statement.

#![allow(unused)]
fn main() {
let alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

let bravo = alfa.get(100);

match bravo {
  Some(bravo) => {
    println!("bravo is {}", bravo);
  }
  None => {
    println!("No value at index 100");
  }
}
}

This is an example of the panic/crash if you try to call an index that doesn't exist with the &alfa[] format.

#![allow(unused)]
fn main() {
let alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

let bravo = &alfa[100];
}

Note that when you use the &alfa[2] format to get a value the program will panic and crash if the index is higher than the number of items availabe.

Maybe that means you should use get but only put it in after you talk about Option<&T>?

Or, maybe you leave this hear and address it later.


Like variables, having a mutable refernce to a vec means trying to add an immutable one won't be allowed. For example, pulling out one value via a reference then trying to update the vec by adding a new number

#![allow(unused)]
fn main() {
let mut alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

let bravo = &alfa[2];

alfa.push(
  String::from("date")
);

println!("bravo is {bravo}");
}

The same scoping behavior that applies to variables applies to vecs. If the last time bravo is used is before the .push() to alfa everything will work because bravo goes out of scope which means the reference disappears.

#![allow(unused)]
fn main() {
let mut alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

let bravo = &alfa[2];
println!("bravo is {bravo}");

alfa.push(
  String::from("date")
);
}

Iterating over a vector:

#![allow(unused)]
fn main() {
let alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

for bravo in &alfa {
  println!("bravo is {bravo}");
}
}

Mutable version

#![allow(unused)]
fn main() {
let mut alfa = vec![
  String::from("apple"),
  String::from("berry"),
  String::from("cherry")
];

for bravo in &mut alfa {
  bravo.push_str("pie")
}

for charlie in alfa {
  println!("charlie is {charlie}")
}
}

TODO: Check in the *i += 50 derefernce format on the "Stroing Lists of Values with Vectors" page


Using Enums to hold multiple types.

#![allow(unused)]
fn main() {
enum Holder {
  Widget(String),
  Thing(String)
}

let holders = vec![
  Holder::Widget(String::from("alfa")),
  Holder::Thing(String::from("bravo"))
];

for item in &holders {
  match item {
    Holder::Widget(value) => {
      println!("got widget with {value}");
    }
    Holder::Thing(value) => {
      println!("got thing with {value}");
    }
  }
}
}

Strings

Create a string with text you already know:

let alfa = String::from("apple");

Create an empty string. Probably want to make it mutable so you can add stuff to it.

let mut alfa = String::new();

Add text

alfa.push_str("pie");

Adding a single character with .push()

alfa.push("s");

Concationation -

Use format!() which is like println!() but it makes a new string instead of printing to output.

#![allow(unused)]
fn main() {
let alfa = String::from("apple");
let bravo = String::from("berry");

let charlie = format!("{}{}", alfa, bravo);

println!("{alfa} {bravo} {charlie}");
}

at some point put in the details about using + and how ownership works. This is for a later chapter. For now just stick with format!(). Note that ownership of alfa gets moved to charlie so it can't be used in the println!()

#![allow(unused)]
fn main() {
let alfa = String::from("apple");
let bravo = String::from("berry");

let charlie = alfa + &bravo;

println!("{bravo} {charlie}");
}

Iterating over a string with .chars()

#![allow(unused)]
fn main() {
let alfa = String::from("apple");

for character in alfa.chars() {
  println!("character is {character}");
}

println!("alfa is {alfa}");
}

TODO: Go over why &alfa[0..4] is tricky because with UTF-8 that's 2 characters in Russian.


Strings are always valid UTF-8


.replace

makes a new string, but it doesn't take ownership so alfa is still available

#![allow(unused)]
fn main() {
let alfa = String::from("the quick fox");
let bravo = alfa.replace("quick", "slow");

println!("alfa is {alfa}");
println!("bravo is {bravo}");
}

.contains()

TODO: Figures out when to show as_str() stuff.

#![allow(unused)]
fn main() {
let alfa = String::from("the quick fox");
let bravo = String::from("quick");

if alfa.contains(bravo.as_str()) {
  println!("found {bravo}");
} else {
  println!("did not find {bravo}");
}
}

Strings Original Text

We've seen two data types so far: i32 and bool. The first represents a number and the second is a value that's either true or false. Now let's start playing with text. To do that we'll use the String data type.

We only need to put the value we want to use on the right side of the = sign to create i32 and bool type values. The String type is a little more complex. It uses this pattern:

String::from("brown");

Creating a String and using it looks like this:

SOURCE CODE

fn main() {
  let color = String::from("brown");
  println!("The quick {color} fox");
}

CODE RUNNER

Mutable Strings

Like the other Rust types, String variables are immutalbe by default. They can be made mutable with the same mut keyword as the other types as well.

We can do things like add more text to a String if it's mutable. That can be done with the method:

.push_str("brown fox")

For example, this prints out

the quick brown fox

SOURCE CODE

fn main() {
  let mut alfa = String::from("the quick ");
  alfa.push_str("brown fox");

  println!("{}", alfa);
}

CODE RUNNER

To Examine


Truncate strings:

#![allow(unused)]
fn main() {
let mut alfa = String::from("apple");

alfa.truncate(2);

println!("alfa is {alfa}");
}

Pop - removes the last value and returns it. (Seems to return it into a Some/None option)

#![allow(unused)]
fn main() {
let mut alfa = String::from("apple");

let bravo = alfa.pop();

match bravo {
  Some(value) => {
    println!("alfa {alfa} bravo {value}");
  }
  None => {
    println!("alfa {alfa} bravo is None");
  }
}
}

There's .remove() but it talks about a byte position which seems like not the happy path way to go. Same with .remove_matches(), .insert(), and .insert_matches()


.len() - Returns the length of this String, in bytes, not chars or graphemes. In other words, it might not be what a human considers the length of the string.


`.is_empty() - Returns true if this String has a length of zero, and false otherwise.


.clear() - Truncates this String, removing all contents.


.chars()

Returns an iterator over the chars of a string slice.

It’s important to remember that char represents a Unicode Scalar Value, and might not match your idea of what a ‘character’ is. Iteration over grapheme clusters may be what you actually want. This functionality is not provided by Rust’s standard library, check crates.io instead.


.split_whitespace()

Splits a string slice by whitespace.

The iterator returned will return string slices that are sub-slices of the original string slice, separated by any amount of whitespace.

‘Whitespace’ is defined according to the terms of the Unicode Derived Core Property White_Space. If you only want to split on ASCII whitespace instead, use split_ascii_whitespace.


.lines(&self)


.contains


.find()

Returns the byte index of the first character of this string slice that matches the pattern.

Returns None if the pattern doesn’t match.


.rfind()

Returns the byte index for the first character of the last match of the pattern in this string slice.

Returns None if the pattern doesn’t match.


.split and .rsplit

An iterator over substrings of this string slice, separated by characters matched by a pattern.

The pattern can be a &str, char, a slice of chars, or a function or closure that determines if a character matches.


.split_inclusive


.split_terminator


.splitn and .rsplitn


.split_once and .rsplit_once


.matches


.matches_indices


.trim

Returns a string slice with leading and trailing whitespace removed.


.trim_matches


.strip_prefix

Returns a string slice with the prefix removed.

If the string starts with the pattern prefix, returns substring after the prefix, wrapped in Some. Unlike trim_start_matches, this method removes the prefix exactly once.

If the string does not start with prefix, returns None.


is_ascii(&self)

Checks if all characters in this string are within the ASCII range.


eq_ignore_ascii_case


make_ascii_uppercase


.replace()

Replaces all matches of a pattern with another string.

replace creates a new String, and copies the data from this string slice into it. While doing so, it attempts to find matches of a pattern. If it finds any, it replaces them with the replacement string slice.


.to_lowercase()


.repeat()

Creates a new String by repeating a string n times.

Hash Maps

They aren't in the prelude. They must be added with use std::collections::HashMap; to make them available.

From the book

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);

println!("score {score}")
}

NOTE: Tried to setup the value with a string like this but it got an error:

scores.insert(String::from("Blue"), 10);

I think that's because .copied() is for i32. Overall, need to walk through that style syntax.


String keys and values get moved into the hashmap.

It's possible to use references with lifetimes


Overwriting a value (from the book)

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut widget = HashMap::new();

widget.insert(String::from("alfa"), 3);
widget.insert(String::from("alfa"), 7);

println!("{:?}", widget)
}

Only add a values if it doesn't alrady exist.

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut widget = HashMap::new();

widget.entry(String::from("alfa")).or_insert(3);
widget.entry(String::from("alfa")).or_insert(7);

println!("{:?}", widget)
}

This is from the book. Need to better understand it (and maybe come up with a refined example)

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let text = "hello world wonderful world";

let mut map = HashMap::new();

for word in text.split_whitespace() {
  let count = map.entry(word).or_insert(0);
  *count += 1;
}

println!("{:?}", map);
}

Prototype 1

Source Code

Function Moves

Using a variable as an argument to a function applies the same type of ownership move. This works right now because we don't try to access alfa again after sending it to the function.

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  widget(alfa);
}

fn widget(value: String) {
  println!("widget got {value}");
}

CODE RUNNER

Function Move Errors

However, if we try to use alfa again by adding this line it'll break with the same type of error as before.

println!("alfa has {alfa}");

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  widget(alfa);

  println!("alfa has {alfa}");
}

fn widget(value: String) {
  println!("widget got {value}");
}

CODE RUNNER

if Expressions

if expressions are used to determine if a section of code should be run or not. They are created with:

  1. The if keyword
  2. A condition to check
  3. A block of code to run if the condition is true.

The condition in the example below checks to see if the number 3 is less than the number 4 using the < math symbol like this:

3 < 4

Since the 3 is less than 4 the condition is true so we'll see the output the condition is true.

SOURCE CODE

fn main() {
  if 3 < 4 {
    println!("the condition is true");
  }
}

CODE RUNNER

If/Else

We can provide an alternate block of code to run for situations where the condition in the if expression is not true. This is done with an else expression.

The below example checks to see if 8 is less than 7 with:

8 < 7

That's not a true statement so if sees the condition as false. Since if only runs its code block if the condition is true it gets passed over. The code in the else block is run instead and we see the output:

the condition is false

SOURCE CODE

fn main() {

  if 8 < 7 {
    println!("the condition is true");
  } else {
    println!("the condition is false");
  }

}

CODE RUNNER

Comparison Operators

We used < to check if one number was less than another like this:

if 1 < 2 {
  println!("The comparison is true");
}

The < symbol is called an "operator". It performs a comparison operation and give us a result.

The < looks the same as you see it in everyday math. Some of the other comparison operators look a little different:

OperatorDescription
<Less than
<=Less than or equal to
>Greater than
>=Greater than or equal to
==Equal to
!=Not equal to

Here's what using == to check if two numbers have the same value looks like:

SOURCE CODE

fn main() {

  if 7 == 8 {
    println!("They match");
  } else {
    println!("They don't match");
  }

}

CODE RUNNER

Variables With If Expressions

Let's use variables in the condition section of our if/else expression. The first step is to bind the values to our variables.

let alfa = 7;
let bravo = 8;

Then, we'll replace the numbers in the condition section of the if statement with the variable names. So, this:

if 7 == 8 {
  ...
}

Becomes this:

if alfa == bravo {
  ...
}

Here's the code for the full example which outputs:

They don't match

SOURCE CODE

fn main() {

  let alfa = 7;
  let bravo = 8;

  if alfa == bravo {
    println!("They match");
  } else {
    println!("They don't match");
  }

}

CODE RUNNER

println! Variables

Binding values to variables let us use them repeatedly. For example, we can improve the output of our program by including the numbers that match or don't match.

We'll do this by using the variables in println!() the same way we have before. The only difference this time is that we're putting two variables in the format string instead of one.

SOURCE CODE

fn main() {

  let alfa = 7;
  let bravo = 8;

  if alfa == bravo {
    println!("{alfa} matches {bravo}");
  } else {
    println!("{alfa} does not match {bravo}");
  }

}

CODE RUNNER

Data Types

Rust has "data types". Each one defines a different kind of data. Every value in Rust has to fit into one of those types. For example, numbers without decimal points have a type of i32.

One way to think about it is like asking someone what kind of dog they have.

What kind of dog is Charlie?

A golden retriever

We can ask a similar question about values in Rust:

What type of variable is alfa?

An i32

The i32 Type

The first type we'll talk about is i32. It's a number without decimal places. We've been using it without calling its type out by name.

When we need to think about types we can now say that we're binding an i32 value of 7 to the varailbe name alfa

SOURCE CODE

fn main() {
  let alfa = 7;
  println!("alfa is {alfa}");
}

CODE RUNNER

Function Arguments

Functions can receive values from other parts of the program when they are called.

To do this, Rust needs to know what type of vaule the function can accept. We specify that in the () parenthesis that follow the functions name in the definition.

For our example, we'll use:

(value: i32)

value is a variable name that the incoming parameter gets bound to inside the function. That's followed by a : that acts as a separator and then the type of value the function expects (i32 in this case).

To use this, we'll change our function from:

fn alfa() {
  ...
}

To

fn alfa(value: i32) {
  ...
}

Sending a value to the function is done by putting it inside the () parenthesis when after the name of the fuction in the call.

So this:

alfa();

Becomes this when we pass the value 7 (which is an i32)

alfa(7);

Here's a full version of the program that outputs:

call alfa next
alfa got 7

SOURCE CODE

fn main() {
  println!("call alfa next");
  alfa(7);
}

fn alfa(value: i32) {
  println!("alfa got {value}");
}

CODE RUNNER

Program: Conditional Functions

We've covered a lot of gound: variables, if/else, comparison operators, functions, and the i32 data type. Each of the examples we used was scoped down as much as possible to focuse on just the concept at hand. Now, let's write a larger program with everything we've learned that does a little more.

Here's what we'll end up with:

  • A check_numbers function that takes two i32 values.
  • An if/else expression that determines if the first value is less than the seond
  • println!() outputs that use the values
  • A set of i32 variables in our main function
  • Calls to the check_numbers function with the sets of variables.

Here's the source which outputs:

4 is less than 9
7 is not less than 3

SOURCE CODE

fn main() {
  let alfa = 4;
  let bravo = 9;
  check_numbers(alfa, bravo);

  let charlie = 7;
  let delta = 3;
  check_numbers(charlie, delta);
}

fn check_numbers(value1: i32, value2: i32) {
  if value1 < value2 {
    println!("{value1} is less than {value2}");
  } else {
    println!("{value1} is not less than {value2}");
  }
}

CODE RUNNER

Immutable Variables

Our Contional Function program uses four variables:

let alfa = 4;
let bravo = 9;

and

let charlie = 7;
let delta = 3;

Another approach is to change the values of alfa and bravo instead of creating charlie and delta. We can do that, but we need to change the way we create alfa and bravo first because Rust variables are immutalbe by default. That means that once we bind a value to them we can't change it.

For example, run this and you'll get an error that we'll discuss on the next page.

SOURCE CODE

fn main() {
  let alfa = 7;
  println!("alfa is {}", alfa);

  alfa = 9;
  println!("alfa is {}", alfa);
}

CODE RUNNER

Assigning Twice Error

When we created a variable with:

let alfa = 7;

and then tried to change it's value with this:

alfa = 9;

We got this error:

   Compiling playground v0.0.1 (/playground)
error[E0384]: cannot assign twice to immutable variable `alfa`
 --> src/main.rs:6:3
  |
3 |   let alfa = 7;
  |       ----
  |       |
  |       first assignment to `alfa`
  |       help: consider making this binding mutable: `mut alfa`
...
6 |   alfa = 9;
  |   ^^^^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `playground` due to previous error

Rust's error messages are verbose, but great. They give us nice, direct statements about what's wrong. Take this line from our error.

error[E0384]: cannot assign twice to immutable variable `alfa`

We made a first assignment to alfa in this default way which make it immutable:

let alfa = 7;

Later, we tried to update the value in the variable by assining 9 to it like this.

alfa = 9;

Rust won't let us do that since alfa is immutalbe so we get that error message which tells us what's happening.

Something that's great about Rust is that error messages often contian recommendations on how to fix what went wrong. Take this line for example:

help: consider making this binding mutable: `mut alfa`

It suggestions not only that we make alfa mutable, but it also shows up how.

Mutable Variables

Rust variables are immutalbe by default. That means you can't change them after they've been set. The mut keyword makes them mutable allowing them to be changed after they are created.

Here's an example of creating a default (immutable) variable:

let alfa = String::from("apple");

And this is how to create one using the mut keyword to make it mutable:

let mut alfa = String::from("apple");

Program: Conditional Functions V2

Here's a minor update to our function to use mutalbe variables by changing this:

let alfa = 4;
let bravo = 9;
check_numbers(alfa, bravo);

let charlie = 7;
let delta = 3;
check_numbers(charlie, delta);

To this:

let mut alfa = 4;
let mut bravo = 9;
check_numbers(alfa, bravo);

alfa = 7;
bravo = 3;
check_numbers(alfa, bravo);

SOURCE CODE

fn main() {
  let mut alfa = 4;
  let mut bravo = 9;
  check_numbers(alfa, bravo);

  alfa = 7;
  bravo = 3;
  check_numbers(alfa, bravo);
}

fn check_numbers(value1: i32, value2: i32) {
  if value1 < value2 {
    println!("{value1} is less than {value2}");
  } else {
    println!("{value1} is not less than {value2}");
  }
}

CODE RUNNER

For Loops

Rust has for loops which allow us to run the same block of code multiple times.

The formula for a for loop that runs through a range of numbers is:

  1. The for keyword
  2. A variable name to hold the value of the number on each loop
  3. The in keyword
  4. The range of numbers
  5. The code block to run each time though the loop

The range has the format of:

  1. The starting number
  2. Two dots followed by an equal sign (i.e. ..=)
  3. The ending number

For example:

1..=5

And here's a full example that outputs:

alfa is 1
alfa is 2
alfa is 3
alfa is 4
alfa is 5

SOURCE CODE

fn main() {
  for alfa in 1..=5 {
    println!("alfa is {alfa}");
  }
}

CODE RUNNER

With Variables

We can also use variables for the starting and ending numbers of the range. Instead of:

1..=5

We set start and end variables then use them with:

start..=end

Here's the updated version that also outputs:

alfa is 1
alfa is 2
alfa is 3
alfa is 4
alfa is 5

SOURCE CODE

fn main() {

  let start = 1;
  let end = 5;

  for alfa in start..=end {
    println!("alfa is {alfa}");
  }

}

CODE RUNNER

Arithmetic Operators

Rust lets you do math through arithmetic operators. They are:

OperatorDescription
+Addition
-Subtraction
*Multiplication
/Division
%Remainder

Addition, subtraction, and multiplication all work the way you'd expect on i32 numbers. For example, this will give us values of 9, -1, and 20 for the three variables.

SOURCE CODE

fn main() {
  let alfa = 4 + 5;
  let bravo = 4 - 5;
  let charlie = 4 * 5;

  println!("alfa is {}", alfa);
  println!("bravo is {}", bravo);
  println!("charlie is {}", charlie);
}

CODE RUNNER

Division Operator

When used with i32 type values the / operator for division works differently than you might expect. For example, dividing 9 by 2 on a calculator yields 4.5. Since i32 values don't have decimal points we only get the portion on the left side.

Running this will output:

alfa is 4

SOURCE CODE

fn main() {
  let alfa = 9 / 2;
  println!("alfa is {}", alfa);
}

CODE RUNNER

Remainder Operator

The remainder operator gives us the value that's left over after putting one number into another as many times as possible. (TODO: Look up the math term for this)

For example, 2 goes into 9 four times with a remainder of 1. So, this code outputs:

alfa is 1

SOURCE CODE

fn main() {
  let alfa = 9 % 2;
  println!("alfa is {alfa}");
}

CODE RUNNER

Arithmetic Operators With Comparison Operators

The value returned by an arithmetic expressoin can be used to set a value like we've already seen:

let alfa = 4 + 9;

They can also be used directly as one half of an if expression with a conditional operator.

For example, instead of:

#![allow(unused)]
fn main() {
if 7 < 10 {
  ...
}
}

We could do:

#![allow(unused)]
fn main() {
if 3 + 4 < 10 {
  ...
}
}

We could even use arithmetic operations on both sides of the = sign like this example that outputs:

is the value more?
no

SOURCE CODE

fn main() {
  println!("is the value more?");
  if 3 + 4 > 5 + 5 {
    println!("yes");
  } else {
    println!("no");
  }
}

CODE RUNNER

Comparison Operators With Variables

We can also use variables for the values to compare.

is the value less?
no

SOURCE CODE

fn main() {
  let alfa = 7;
  let bravo = 3;
  let charlie = 9;

  println!("is {alfa} + {bravo} less than {charlie}?");

  if alfa + bravo < charlie {
    println!("yes");
  } else {
    println!("no");
  }
}

CODE RUNNER

Using The Remainder

The % remainder operator is surprigingly useful in combination with the comparison operators. One example is using it to divide content into groups.

For exmaple, if we use % with an incrementing number on the left side and 3 on the right side it will return zero for everything that's divisible by three with no remainder. If we run a comparison against that value in an if statement we can process every third value differently.

Take this code for example that outputs:

1 <----
2 <----
----> 3
4 <----
5 <----
----> 6
7 <----
8 <----
----> 9
10 <----

SOURCE CODE

fn main() {
  let start = 1;
  let end = 10;
  let target = 3;

  for alfa in start..=end {
    if alfa % target == 0 {
      println!("----> {alfa}")
    } else {
      println!("{alfa} <----")
    }
  }
}

CODE RUNNER

Using The Remainder V2

Here's the same thing, but using 4 for the value on the right side:

The output changes from:

1 <----
2 <----
----> 3
4 <----
5 <----
----> 6
7 <----
8 <----
----> 9
10 <----

To:

1 <----
2 <----
3 <----
----> 4
5 <----
6 <----
7 <----
----> 8
9 <----
10 <----

To this by just chaning the single target number from 3 to 4.

SOURCE CODE

fn main() {
  let start = 1;
  let end = 10;
  let target = 4;

  for alfa in start..=end {
    if alfa % target == 0 {
      println!("----> {alfa}")
    } else {
      println!("{alfa} <----")
    }
  }
}

CODE RUNNER

Booleans

We've used a bunch of i32 type values. It's time to introduce our next data type: bool

The bool data type stands for boolean. A value that can be either true or false and nothing else.

Boolean values are bound to variables like this:

let alfa = true;
let bravo = false;

The if statements we've been using check to see if the condition is true or not. We can use bool values

SOURCE CODE

fn main() {
  let mut alfa = true;
  let mut counter = 1;

  while alfa == true {
    println!("counter is {}", counter);
    if counter == 5 {
      alfa = false;
    }
    counter = counter + 1;
  }
}

CODE RUNNER

In Conditionals

if statements check a condition to see if it's true or false. We've been using expressions like 3 < 4 for as those conditions. We can also use bool type variables.

For example, this binds the bool value false to the alfa variable then uses it as the condition in the if statement.

The output is:

alfa is false

SOURCE CODE

fn main() {
  let alfa = false;

  if alfa {
    println!("alfa is true");
  } else {
    println!("alfa is false");
  }
}

CODE RUNNER

Function Return Values

We've set up functions to receive argumets by putting a name and type inside the parenthesis after the function name:

fn alfa(value: i32) {
  println!("alfa got {}", value);
}

So far, all the functions have printed something out when called. Now we'll get them to return data to whatever called them. Setting that up is done by adding -> after the parenthesis followed by the type of data that will be returned.

The data that gets returned is generated by the last expression in the function's code block. For example, this double_number function takes an i32 value and multiplies it by 2 as it's sending it back.

SOURCE CODE

fn main(){
  let alfa = double_number(5);
  println!("alfa is {}", alfa);
}

fn double_number(value: i32) -> i32 {
  value * 2
}

CODE RUNNER

Different Types

Here's another look at the privous function:

fn double_number(value: i32) -> i32 {
  value * 2
}

It takes an i32:

double_number(value: i32)

And returns an i32:

-> i32

The input type and the output type don't have to match. In fact, return values can be sent from functions that don't receive any arguments. Here we create a function that takes no arguments and always returns true.

SOURCE CODE

fn main() {

  let alfa = get_true();
  println!("alfa is {}", alfa);

}

fn get_true() -> bool {

  true

}

CODE RUNNER

Returning From if

A function that always returns true isn't particually useful. A more realistic example is a function that returns true based on some condition. This example uses a function to determine if a value is above 5.

SOURCE CODE

fn main() {

  let alfa = 7;
  let bravo = is_over_five(alfa);

  println!("{} is over five? {}", alfa, bravo);

}

fn is_over_five(value: i32) -> bool {

  if value > 5 {
    true
  } else {
    false
  }

}

CODE RUNNER

Functions As Conditions

We can use the return values as conditions:

SOURCE CODE

fn main() {
  let alfa = 7;

  if check_number(alfa) {
    println!("got true");
  } else {
    println!("got false");
  }
}

fn check_number(value: i32) -> bool {
  if value == 7 {
    true
  } else {
    false
  }
}

CODE RUNNER

Program 3 NAME TKTKTKT

TODO: Make a version of this with a Vec that returns both the bool and the i32.

SOURCE CODE

fn main() {
  let start = 1;
  let end = 100;
  let splitter = 8;

  for counter in start..=end {
    if should_process(counter, splitter) {
      let how_many_left = count_how_many_left(counter, end);
      println!("at {counter} with {how_many_left} remaining");
    }
  }
}

fn should_process(value1: i32, value2: i32) -> bool {
  if value1 % value2 == 0 {
    true
  } else {
    false
  }
}

fn count_how_many_left(value1: i32, value2: i32) -> i32 {
  value2 - value1
}


CODE RUNNER

Scopes

TKTKTK Come up with better descriptions and diagrams for scope.

Rust uses scopes to define what variables are availalbe in different parts of applicaitons.

One way to think about scopes is like one big box a collection of smaller boxes inside it. Each smaller box can have more boxes inside of them and so on and so forth. A value stored in a box is avaialble to that box and any box surround it on the path up to the top box. But, boxes on the sides can't access it.

> global            1
> global:main       2   fn main() {
> global:main       3     println!("in main");
> global:main       4   }
> global            5

Difficulty Increasing

A quick heads: we're getting into sections that deal with the more conceptually complicated parts of Rust. They're regarded as the harder part of learning the language.

Going through them multiple times before things click happenes more often than not.

Illustrations Incoming

My handwriting is chicken scratch. The upcoming illustrations are drafts. They'll get replaced once I've got things more finalized.

References Old

Mutable References

Mutable references are created using &mut instead of & to make the reference. The first variable must be created with mut too. When we make modifications to the reference the value change shows up in the alfa variable too.

The steps for this example are:

  • Create a mutable variable named alfa with a String bound to it
  • Print alfa to display the value
  • Create a variable named bravo that has a mutable reference to alfa
  • Update the value in bravo with .push_str()
  • Print out bravo to display its value
  • Print out alfa again to show that the value updated there too

The output of this code is:

alfa has apple
bravo has applepie
alfa has applepie

SOURCE CODE

fn main() {
  let mut alfa = String::from("apple");
  println!("alfa has {alfa}");

  let bravo = &mut alfa;
  bravo.push_str("pie");

  println!("bravo has {bravo}");
  println!("alfa has {alfa}");
}

CODE RUNNER

A Single Value

TKTKTKT

SOURCE CODE

fn main() {
  let mut alfa = String::from("widget");
  println!("alfa is {alfa}");

  {
    let bravo = &mut alfa;
    println!("bravo is {bravo}");
    bravo.push_str("-thing");
    println!("bravo is {bravo}");
  }

  println!("alfa is {alfa}");

}

CODE RUNNER

Using In A Function

This is how to pass a reference to a function where you don't loose ownership

SOURCE CODE

fn main() {
  let alfa = String::from("apple");
  println!("alfa is {alfa}");

  show_value(&alfa);
  println!("alfa is {alfa}");
}

fn show_value(param: &String) {
  println!("show_value got {param}");
}

CODE RUNNER

Changing In A Function

SOURCE CODE

fn main() {
  let mut alfa = String::from("widget");
  println!("alfa is {alfa}");

  update_value(&mut alfa);
  println!("alfa is {alfa}");
}

fn update_value(value: &mut String) {
  value.push_str("-thing");
}

CODE RUNNER

A Function Error

Ownership And Moving

Every value in Rust has an "owner". For example, this statement assigns a String with the value of apple to the variable alfa. That makes alfa the owner of the String.

let alfa = String::from("widget");

If we create a new variable called bravo by binding the value of alfa to it, the ownership of the String passes from alfa to bravo. So this works and will output

alfa contains widget
bravo contains widget

SOURCE CODE

fn main() {
  let alfa = String::from("widget");
  println!("alfa contains {alfa}");

  let bravo = alfa;
  println!("bravo contains {bravo}");
}

CODE RUNNER

Moving Strings Error

Moving a value means it's no longer in the place it was before. (That is, it's really a move and not a copy)

So, when the value of alfa gets moved into bravo it's no longer available in alfa. Trying to use it throws an error like this example:

SOURCE CODE

fn main() {
  let alfa = String::from("widget");
  println!("alfa contains {alfa}");

  let bravo = alfa;
  println!("bravo contains {bravo}");

  println!("alfa contains {alfa}");
}

CODE RUNNER

Cloning And Referencing

Trying to access a value after we moved it to a new variable resulted in an error message that contined this line:

help: consider cloning the value if the performance cost is acceptable

Cloning is done using the .clone() method. Doing so changes this:

let bravo = alfa;

into this:

let bravo = alfa.clone();

When we clone, it makes a new copy of the String that's bound to alfa and then binds that new copy to bravo. Since the value wasn't moved out of alfa we can still use it like this sample which outputs:

alfa is widget
bravo is widget
alfa is widget

SOURCE CODE

fn main() {
  let alfa = String::from("widget");
  println!("alfa contains {alfa}");

  let bravo = alfa.clone();
  println!("bravo contains {bravo}");

  println!("alfa contains {alfa}");
}

CODE RUNNER

Cloning For Separate Values

Cloning the value allows us to modify alfa and bravo independently (assuming we've made them mutable with the mut keyword).

The out we'll get is:

alfa contains widget
bravo contains widget
alfa contains widget-alfa
bravo contains widget-bravo

SOURCE CODE

fn main() {
  let mut alfa = String::from("widget");
  let mut bravo = alfa.clone();

  println!("alfa contains {alfa}");
  println!("bravo contains {bravo}");

  alfa.push_str("-alfa");
  bravo.push_str("-bravo");

  println!("alfa contains {alfa}");
  println!("bravo contains {bravo}");
}

CODE RUNNER

Binding Values From if

if statements are expressions. That means they return a value. So, using one on the right side of an = sign is completely valid.

For example:

SOURCE CODE

fn main() {
  let alfa = true;
  let bravo = if alfa { 1 } else { 0 };

  println!("bravo is {bravo}")
}

CODE RUNNER

Must Be The Same Type

TODO: Put in note earlier about calling the branches arms

Using an if/else expression to determine a variable to bind directly to a variable requires both the if arm and the else arm to provide the same type. So you can have two i32 values like this:

let alfa = true;
let bravo = if alfa { 1 } else { 0 };

or two bool values like this:

let alfa = true;
let bravo = if alfa { true } else { false };

but you can't mix them like this which will give an error:

SOURCE CODE

fn main() {
  let alfa = true;
  let bravo = if true { 1 } else { false };

  println!("bravo is {bravo}");
}

CODE RUNNER

Mismatched Type Error

TKTKTKT - Fill in the details on this error.

Here's the error from the prior page:

   Compiling playground v0.0.1 (/playground)
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:3:36
  |
3 |   let bravo = if true { 1 } else { false };
  |                         -          ^^^^^ expected integer, found `bool`
  |                         |
  |                         expected because of this

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

Sandbox

SOURCE CODE

fn main() {
  let alfa = String::from("quick");
  let bravo = alfa;

  println!("{alfa}");
}
fn main() {
  let alfa = String::from("quick");
  widget(alfa);
  println!("{alfa}");
}

fn widget(value: String) {
  println!("{value}");
}

While Loops

Rust provides while loops in addition to for loops. Mutable variables work well with them by acting as counters.

In this case we'll set a mutable variable named counter for a while loop that checks to see if it's less than or equal to 5.

Everytime through 1 add 1 to the counter with += 1 which takes the current value and adds one to it.

SOURCE CODE

fn main() {

  let mut counter = 1;

  while counter <= 5 {
    println!("counter is {}", counter);
    counter = counter + 1;
  }

}

CODE RUNNER

Variables In println!

The code from the prior page includes this line:

println!("alfa is {alfa}");

which produced this output:

alfa is 7

The characters first piece of text quoted inside println!() is called a "format string". Format strings can output basic text like we saw with the original println!("Hello, World") example. They can also output the value of variables like we're doing with alfa above.

The process works by adding {} curly brackets with the name of the variable inside it. In our case this is:

{alfa}

Here's another example where we set the variable bravo to 12 then print it out.

fn main() {
  let bravo = 12;
  println!("bravo is {bravo}");
}

The String Type

The i32 type allows us to drop in a number on the first side of the = sign with no extra fanfare.

let alfa = ;

The String type requires more work. Creating a String variable looks like this:

let alfa = String::from("Hello");

String are defined like this:

let alfa = String::from("Hello");

With i32 we could simply drop in the number on the right side of the =, like:

let alfa = 7;

the much longer String::from("Hello"). The first word String defines the type. Then, from() is where we pass in the piece of text we want to String to be made of.

SOURCE CODE

fn main() {

  let alfa = String::from("Hello");

  println!("alfa is {}", alfa);

}

CODE RUNNER

Vec

Else If Expressions

There's a third option we can add beside if and else. It's called else if. Using else if creates a new if statement that only runs if the thing before it was false.

Assignment Operators

That while example added a new type of expression we haven't seen yet:

counter += 1;

The += is an "assignment operator". They're like the comparison operators we saw earlier (like <, >= and ==). But, instead of telling us if the expression they are in is true, assignments operators do math on the values on either side.

For example, the += adds whatever is on the right side to the value on the left. So, this will give us an output of

1
2
3

SOURCE CODE

fn main() {
  let mut alfa = 1;
  println!("{}", alfa);

  alfa += 1;
  println!("{}", alfa);

  alfa += 1;
  println!("{}", alfa);
}

CODE RUNNER

Assignment Operator Examples

The different assignment operators are:

OperatorDescription
+=Addition
-=Subtraction
*=Multiply
/=Division
%=Remainder

Addition, subtraction, and multiplication behave like everyday math. Here we'll make an initial set of varilbes and set them to 3. Then we'll add, subtract, and multiple them with 4 using the assignment operators. That will give us:

7
-1
12

SOURCE CODE

fn main() {

  let mut alfa = 3;
  let mut bravo = 3;
  let mut charlie = 3;

  alfa += 4;
  println!("{}", alfa);

  bravo -= 4;
  println!("{}", bravo);

  charlie *= 4;
  println!("{}", charlie);


}

CODE RUNNER

Division

The /= operator for division works a little differently than everyday math. Dividing i32 numbers that go into each other evently works as expected. For example, 6 divided by 2 will result in 3.

However, dividing numbers that don't go into each other evenly results in just the full number of times it can (TODO: Look up the math terms for that.)

Using 2 and 5 doesn't give us 2.5, it gives us just the part on the left side of the decimal (i.e. 2)

SOURCE CODE

fn main() {
  let mut alfa = 5;
  alfa /= 2;

  println!("alfa is {}", alfa);

}

CODE RUNNER

Remainder

We use the %= (remainder) assignment gives us the remainder from a division operation. For example, dividing 9 by 2 results in 2 going into 9 four times with a remainder of 1. That 1 is what the %= will give us.

SOURCE CODE

fn main() {

  let mut alfa = 9;
  alfa %= 2;

  println!("alfa is {}", alfa);
}

CODE RUNNER

Data Types

Rust's design requires every value have a "type" assigned to. The "type" of a value is what you'd used to answer a question about what kind of thing it is. For example, if you were asking someone about their dog:

What kind of dog is Charlie?

A golden retriever

And, in a similar manner when discussing a Rust program:

What type of variable is alfa?

A number with a decimal point

Except we wouldn't use the term "a number with a decimal point". We'd use f32. That's what Rust uses to set the type of a value to a "floating-point number" (which is a number that has a decimal point compared to an "integer" which doesn't).

As we saw in the previous chapter, types are assigned to variables when they are created. We used i32 in the prior examples for integers. We could have used f32 for floating-point numbers just as easily:

For example:

SOURCE CODE

fn main() {

  let alfa: f32 = 3.4;

  println!("Value {}", alfa);

}

CODE RUNNER

Implicit And Explicit

In all our previous examples we've set the type of our variable explicitly by adding a : after the name along with the type (e.g. i32). Rust has the ability to guess the type of some variables so that's not always necessary. When we do that it's called an "implicit" type assignment and it looks like this:

let alfa = 7;

That turns our formula for defining a variable into this:

  1. The let keyword
  2. The optional mut keyword
  3. A name for the variable (e.g. alfa)
  4. A : separator if we're explicitly setting the data type
  5. An optional data type
  6. The = sign
  7. The value to bind to it (e.g. 7)
  8. A ; the ends the definition

In this example, both alfa and bravo have an i32 type. Alfa is defined explicitly. Bravo is defined implicitly.

fn main() {

  let alfa: i32 = 7;

  let bravo = 9;

  println!("Alfa {} - Bravo {}", alfa, bravo)

}

Using Explicit Types

Setting data types implicitly like this:

let alfa = 7;

or explicitly like this:

let alfa: i32 = 7;

are both frequency occurrences in Rust programs. While the implicit version is shorter to type and easier to read we'll be using the explicit approach for a few reasons:

  1. To get used to the idea that every value has a type
  2. To use a consistent format in our examples
  3. Because you can always use explicit compared to implicit types which only work sometimes.

The Number Types

We've seen both f32 for floating point numbers that have a decimal points and i32 for integers that don't. There's a third type of number called u32 which is for "unsigned integer".

A regular i32 integer can be both positive and negative where the negative numbers have a "-" sign in front of them. When we say a u32 is unsigned, it means it can't have a "-" sign. That it turn means it can't be negative.

This brings us to the 32 that follows the f, i, and u in the types. It determines how big a number can be used for the variable.

Here's the values for floating point numbers

typelowest numberhighest number
f8TKTKTKTTKTKTKT
f16TKTKTKTTKTKTKT
f32TKTKTKTTKTKTKT
f64TKTKTKTTKTKTKT
f128TKTKTKTTKTKTKT

Here's the values for integers

typelowest numberhighest number
i8TKTKTKTTKTKTKT
i16TKTKTKTTKTKTKT
i32TKTKTKTTKTKTKT
i64TKTKTKTTKTKTKT
i128TKTKTKTTKTKTKT

And, here's the values for unsigned integers

typelowest numberhighest number
u8TKTKTKTTKTKTKT
u16TKTKTKTTKTKTKT
u32TKTKTKTTKTKTKT
u64TKTKTKTTKTKTKT
u128TKTKTKTTKTKTKT

TODO

  • Fill in the high/low values

  • Add notes about arch

Booleans

Rust has another fundamental data type called "Boolean".

A boolean is either true or false. It can't be anything else. They use the bool keyword and are assigned like this:

let bravo: bool = false;

Used in a full program it looks like this:

SOURCE CODE

fn main() {

  let alfa: bool = true;

  println!("Value {}", alfa);

}

CODE RUNNER

Compound Data Types

The floating-point, integer, and boolean data types we've covered each hold a single value. Collectively, they're called "scalar" types (TODO: find where the term scalar comes from)

Rust has another category of types called "compound" types. They hold multiple values instead of being constrained to a single one.

Compound data types come in two flavors: Tuples and Arrays.

Tuples

Tuples are containers that hold other values of different types.

Creating a tuple variable looks different than making one for a scalar data type. Instead of using a specific keyword after the variable's name (like i32 in let alfa: i32 = 58), tuple definitions use a pair of parenthesis with the types it's going to contain between them.

To the right side of the equal sign the values to assign are placed in a corresponding set of parenthesis.

For example, defining a tuple with three integers looks like this:

let alfa: (i32, i32, i32) = (5, 7, 9);

Making one with a float and a boolean looks like this:

let bravo: (f32, bool) = (37.9, false);

Tuple Indexes

Getting values out of a tuple is done using the variable's name followed by a dot and the "index" number for the position we're after.

For example:

alfa.2

The index position represents a counter that starts at the beginning of the tuple and goes up one for each position. But, the first number is zero instead of one. So, the first position is 0, the second position is 1, etc...

Here's an example creating a tuple that holds an i32, a f32, and a bool and then prints them out.

SOURCE CODE

fn main() {

  let alfa: (i32, f32, bool) = (99, 234.5, false);

  println!("1st {}", alfa.0);
  println!("2nd {}", alfa.1);
  println!("3rd {}", alfa.2);

}

CODE RUNNER

Indexes And Offsets

The name for the way these index numbers work is "zero based index". The way I've learned to think about them is as a position offset.

We always start with the first item position. If we want the value of that item it doesn't require moving. That translates, to zero offset from which means index of 0.

The second number is one away from the start. So, we access it with an index of 1.

To get to the third number requires moving two steps from the start. That gives us and index of 2

Regardless of the way you think about it, working with zero based indexes can take some time to get used to. Even folks who've been programming for years screw it up sometimes.

Functions

Every program we've run so far has consisted of the single main function. It's definition starts with fn followed by the name of the function with some parenthesis (i.e. main()), then the opening and closing {} curly braces that wrap the code that gets executed when the function is called.

We can add more functions to our programs as well using the same approach. For example, here's the definition of a function named "alfa" that prints out "I am alfa":

fn alfa() {
  println!("I am alfa");
}

The function is used by "calling it". The syntax for that is the name of the function followed by parenthesis:

alfa();

Here's a full program where we print I am main inside the main function then call the alfa function which then prints I am alfa

SOURCE CODE

fn main() {
  println!("I am main");
  alfa()
}

fn alfa() {
  println!("I am alfa");
}

CODE RUNNER

Function Parameters

We can also send data to functions for them to use. In order to do this we have to let the functions know a few things. To start with, those are:

  1. That data will be coming in
  2. What type of data it will be
  3. A variable name to use to hold the incoming data

The way we do that is to assign a variable inside the parenthesis after the function name. The format is similar to what we've used to assigning variable so far but we remove the let, =, and the value.

So instead of this:

let alfa: i32 = 3

we'd pull out just this part:

alfa: i32

To use that in a function named widget we'd write this:

fn widget(alfa: i32) {
  println!("I am alfa");
}

TODO: Combine this with the next page since it doesn't really make sense to show the incoming parameter but not use it.

Using Function Parameters

Our widget function defined a parameter called alfa of type i32. In order to use it, we have to pass it in when we call the function. This is done by putting the value we want to use inside the parenthesis when we call the function. For example:

widget(7);

Once we do that we can use the value vai the alfa variable we set inside the function definition.

For example:

fn main() {
  println!("This is main");
  widget(7);
}


fn widget(alfa: i32) {
  println!("This is widget with {}", alfa);
}

Multiple Function Parameters

Defining functions that take more than one parameter is done by adding more in the parentheses that are separated by commas like this:

widget(alfa: i32, bravo: f32)

Here's a full program:

SOURCE CODE

fn main() {
  println!("This is main");
  widget(7, 3.4);
}

fn widget(alfa: i32, bravo: f32) {
  println!("This is widget");
  println!("Alfa {} - Bravo {}", alfa, bravo);
}

CODE RUNNER

Using Multiple Parameters

Parameters don't have to be kept independent when we pass them into a function. For example, if we use to i32 integers we can add them together in a new variable and print it. In this case we'll use a function named do_addition. The output of the program will be:

This is main
do_addition made 8

SOURCE CODE

fn main() {
  println!("This is main");
  do_addition(3, 5);
}

fn do_addition(alfa: i32, bravo: i32) {
  let sum = alfa + bravo;
  println!("do_addition made {}", sum);
}

CODE RUNNER

Function Return Values

The widget function in the previous examples received a value from main but didn't pass anything back. Setting up to do that is a matter of defining the type of data the function will return. This is done by adding a -> and data type after the function name and associated () for the incoming parameters.

The format without any incoming parameters that returns an i32 integer looks like this:

widget() -> i32

Here's what everything looks like in a program. It will output:

widget is 10

SOURCE CODE

fn main() {
  println!("widget is {}", widget());
}

fn widget() -> i32 {
  5 + 5
}

CODE RUNNER

Binding Return Values

The values return from functions can be bound to variables. For example, if we create a variable named alfa of type i32 we can set it to the return value of a widget function that has the same time.

Here's a program which outputs:

alfa is 9

SOURCE CODE

fn main() {
  let alfa = widget();
  println!("alfa is {}", alfa);
}

fn widget() -> i32 {
  3 + 6
}

CODE RUNNER

Return Expressions

The widget function in the last example looked like this:

fn widget() -> i32 {
  3 + 6
}

I bring that up to point out that there isn't a ; after the 3 + 6. This is different from all the other lines we've seen in functions so far.

There are three reasons for this:

  1. Rust functions return the last expression at the end of their code block
  2. The ; ends expressions.
  3. Rust has a default return type that we'll discuss on the next page.

What all that means is if we did this:

3 + 6;

instead of:

3 + 6

we'd be end the expression that did the addition. That expression is what provides the i32 value that the function is setup to return based off its definition. So, we'd end up with an error.

Run the code again with the ; in place to see what the error looks like and we'll talk about it on the next page.

SOURCE CODE

fn main() {
  let alfa = widget();
  println!("alfa is {}", alfa);
}

fn widget() -> i32 {
  3 + 6;
}

CODE RUNNER

Return Expression Errors

(TODO: Split this into multiple pages)

Here's the error message from the prior page where we intentionally put a semi-colon at the end of an expression that caused an error.

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:6:16
  |
6 | fn widget() -> i32 {
  |    ------      ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
7 |   3 + 6;
  |        - help: remove this semicolon to return this value

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

The Rust error message are great but they can be pretty overwhelming at first. Let's to through it piece by piece to get a better idea of what happened.

*****

Compiling playground v0.0.1 (/playground)

This is a note from the rust compiler letting us know what it's working on. The site uses Rust Playground to compile and run code. This is the name it uses on its side.

*****

error[E0308]: mismatched types
 --> src/main.rs:6:16

TKTKTK

*****

6 | fn widget() -> i32 {
  |    ------      ^^^ expected `i32`, found `()`

TKTKTK

*****

6 | fn widget() -> i32 {
  |    ------      ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression

We've added two lines to the two in the prior description.

TKTKTK

*****

7 |   3 + 6;
  |        - help: remove this semicolon to return this value

TKTKTK

*****

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

TKTKTK

Functions Review

This chapter covered:

  • Functions with no parameters
  • Functions with parameters
  • Using parameters
  • Functions with return values
  • Binding return values

Here's a review the combines everything. It outputs

sum is 12

SOURCE CODE

fn main() {
  let sum = do_addition(5, 7);
  println!("sum is {}", sum);
}

fn do_addition(alfa: i32, bravo: i32) -> i32 {
  let response = alfa + bravo;
  response
}

CODE RUNNER

if Expressions

if expressions are used to determine if a section of code should be run or not. They are created with:

  1. The if keyword
  2. A condition to check
  3. A block of code to run if the condition is true.

The condition in the example below checks to see if the number 3 is less than the number 4 using the < math symbol like this:

3 < 4

Since the 3 is less than 4 the condition is true so we'll see the output the condition is true.

SOURCE CODE

fn main() {
  if 3 < 4 {
    println!("the condition is true");
  }
}

CODE RUNNER

false Conditions In if Expressions

The condition that was checked in the prior example (i.e. 3 < 4) was true. Because of that, Rust ran the code inside the code block and printed our "3 is less than 4" output. The block of code won't run if we end up with an if condition that's false instead of true.

In this example we'll do another check. This time will do one that's false. We'll also add a couple println!() expressions to help see what's going on. Specifically, we'll see the output from the two println!() expressions that are outside the if. But, we won't see the one inside the if. It'll looks like this:

alfa
charlie

SOURCE CODE

fn main() {

  println!("alfa");

  if 3 > 4 {
    println!("bravo");
  }

  println!("charlie");

}

CODE RUNNER

else Expressions

The value of a condition in an if expression must be either true or false. If the condition's value is true the code block gets executed. If the condition is false it doesn't.

Executing an alternate code block when an if condition is false is done with an else expression.

This example sets up an if statements that runs if 5 is less than 4. We'll follow it with an else expression that will get run since evaluating 5 < 4 is false

The output will be:

5 is not less than 4

SOURCE CODE

fn main() {

  if 5 < 4 {
    println!("5 is less than 4");
  } else {
    println!("5 is not less than 4");
  }

}

CODE RUNNER

else if Expressions

TODO: Make an earlier example showing using a variable for the condition so you don't have to explain it here.


Rust also provides an else if expression. It goes in between if and else expressions and gets a condition to check like the initial if statement does.

SOURCE CODE

fn main() {

  let value = 5;

  if value < 3 {
    println!("alfa");
  } else if value < 7 {
    println!("bravo");
  } else {
    println!("charlie");
  }

}

CODE RUNNER

Multiple if else Expressions

SOURCE CODE

fn main() {

  let value = 7;

  if value <= 5 {
    println!("alfa");

  } else if value <= 6 {
    println!("bravo");

  } else if value <= 7 {
    println!("charlie");

  } else if value <= 8 {
    println!("delta");

  } else {
    println!("echo");
  }

}

CODE RUNNER

Function Example

SOURCE CODE

fn main() {

  let value = 5;

  if is_value_seven(value) {
    println!("yes");
  } else {
    println!("no");
  }

}

fn is_value_seven(check_value: i32) -> bool {
  check_value == 7
}

CODE RUNNER

Binding Variables

NOTE: Since we're using the if with let it must have a ; after the last part of the expression.

SOURCE CODE

fn main() {

  let alfa = true;

  let bravo = if alfa {
    0
  } else {
    1
  };

  println!("bravo is {}", bravo);
}

CODE RUNNER

Binding Values Must Be The Same

This will break via: https://doc.rust-lang.org/book/ch03-05-control-flow.html because the values from the two arms of the if aren't the same type. This will throw an error that we'll look at on the next page.

SOURCE CODE

fn main() {

  let alfa = if 3 <= 4 {
    0
  } else {
    false
  };

  println!("alfa is {}", alfa);

}

CODE RUNNER

Incompatible Types

TODO: Build out this page with the error from the prior page.

SOURCE CODE

   Compiling playground v0.0.1 (/playground)
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:6:5
  |
3 |     let alfa = if 3 <= 4 {
  |  ______________-
4 | |     0
  | |     - expected because of this
5 | |   } else {
6 | |     false
  | |     ^^^^^ expected integer, found `bool`
7 | |   };
  | |___- `if` and `else` have incompatible types

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

CODE RUNNER

While Loops

The condition check is built in.

SOURCE CODE

fn main() {
  let mut alfa = 1;

  while alfa <= 5 {
    println!("alfa is {}", alfa);
    alfa += 1;
  }
}

CODE RUNNER

Assignments From While Loops

TODO: Figure out if you can do this or not.

SOURCE CODE

fn main() {
  let mut alfa = 1;

  let bravo = while alfa <= 5 {
    println!("alfa is {}", alfa);
    alfa += 1;
    alfa * 2
  };

  println!("bravo is {}", bravo);

}

CODE RUNNER

For Loops

TODO: Switch to using a Vec instead of and array probably since Vecs are what will have been used elsewhere.

SOURCE CODE

fn main() {

  let alfa = [5, 10, 15, 20];

  for item in alfa {
    println!("item is {}", item)
  }
}

CODE RUNNER

Using Ranges In For Loops

SOURCE CODE

fn main() {

  for alfa in (1..10) {
    println!("alfa is {}", alfa)
  }

}

CODE RUNNER

Error Messages In Tutorials

Getting hit with a typo error when you're trying to learn something else is a waste of time, energy, and motivation.

The Status line is there to prevent type errors, but we won't ignore error messages. Learning to read them is a critical part of learning a programming language.

Error messages can come from two categories of mistakes:

  1. Doing something wrong
  2. Typing something wrong

We'll look at both types, but we'll do it intentionally using Source Code blocks specificaly designed to demonstrate them.

The first thing is to point out that "wrong" in this context is not bad. It's more like when you walk up to a door and try to open it by pushing instead of pullling. You tried to do the wrong thing, but there's nothing bad about it. You do it another way until you find something that works.

We'll look at both types of error messages

Understanding error messages is a critical part of learning a language. But, they're best studied explicitly. During a tutorial, error messages from

Messing With The Code

The Status line typo warnings won't prevent you from running code. You can fiddle with the examples and run them to see what happens even though the warning will let you know you've drifted away from the example source code.

I've got a feature on the road map to disable the typo catcher. That'll make playing with the examples more friendly since it won't be throwing the warning at you when you change things.

Preamble

There are a few other things to cover before we get started with the code.

The Content

  • These are my actual, in-progress notes on learning Rust. As such, the site is very much a work in progress. It only goes far as I've made it in my studies.

  • The content is designed for folks who have at least some programming experience. If you know what variables, functions, loops, and conditionals are, you'll be fine.

The Examples

  • Examples are kept intentionally sort. That often means doing things that would be silly in a useful program. For example, defining a variable that's used only once on the following line. The goal is to show how to use something not when to use it.

  • Examples use the NATO phonetic alphabet (alfa, bravo, charlie, etc...). The words have no meaning beyond being labels or acting as content.

Typing It In

  • The approach of typing in examples may feel weird and frustrating at first if you've never done it. Give it 5-10 pages to see if you start to find it useful.

  • Of course, you can also read the site like a book without typing anything in if you learn better that way.

Now, let's get started with a better look at Hello, World.

The String Type

TODO: Figure out if you want to do string literals before this or not. I'm thinking not so that we only have to think about a single string type for the early part of the book. Push the literals to later.

Example showing how to add more text to a mutable String

SOURCE CODE

fn main() {

  let mut alfa = String::from("Hello");
  println!("alfa is {}", alfa);

  alfa.push_str(", World");
  println!("alfa is {}", alfa);

}

CODE RUNNER

Moving String

Trying to do this won't work (via https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html)

fn main() {

  let alfa = String::from("widget");
  let bravo = alfa;

  println!("alfa {} - bravo {}", alfa, bravo);

}

We'll look at the error message on the next page, but for now, here's what will work.

TKTKTK

SOURCE CODE

fn main() {

  let alfa = String::from("widget");
  let bravo = alfa.clone();

  println!("alfa {} - bravo {}", alfa, bravo);

}

CODE RUNNER

TODO: Write up how integers can be copied with:

let alfa = 7;
let bravo = alfa;

Functions Placeholder

This is a place holder until these function pages are sorted.

Passing Values In Functions

passing a variable into a function works the same way as assigning it to a new variable.

this throws an error:

fn main() {

  let alfa = String::from("apple");

  widget(alfa);

  println!("alfa {}", alfa);

}

fn widget(incoming: String) {
  println!("Got {}", incoming)
}

TKTKTKT - Talk about how integers can work

this is okay:

fn main() {

  let alfa = 7;

  widget(alfa);

  println!("alfa {}", alfa);

}

fn widget(incoming: i32) {
  println!("Got {}", incoming)
}

You can pass a string and get one back if you want to do that.

But check out references next. Maybe don't get into passing back right now and just focus on references.

Function References

You can pass values by reference so the functions don't take ownership.

TKTKTK The act of using a reference is called borrowing.

SOURCE CODE

fn main() {

  let alfa = String::from("apple");

  widget(&alfa);

  println!("alfa {}", alfa)

}

fn widget(incoming: &String) {
  println!("incoming: {}", incoming)
}

CODE RUNNER

Function References Can't Be Modified By Default

TKTKTKT

This won't work by default. You have to do the mutalbe version which is on the next page.

SOURCE CODE

fn main() {

  let alfa = String::from("widget");

  attempt_change(&alfa);

}

fn attempt_change(value: &String) {
  value.push_str("update");
}

TODO: Show the error message.

Mutable Function References

It is possible to change values in a function if you make the variable mutable and pass a mutable reference

SOURCE CODE

fn main() {

  let mut alfa = String::from("widget");

  println!("alfa {}", alfa);

  update_value(&mut alfa);

  println!("alfa {}", alfa);

}

fn update_value(value: &mut String) {
  value.push_str(" updated");
}

CODE RUNNER

Only One Mutable Reference

You can only make one mutable reference to a variable.

This will fail:

SOURCE CODE

fn main() {

  let mut alfa = String::from("widget");

  let bravo = &mut alfa;

  let charlie = &mut alfa;

  println!("bravo {}", bravo);
  println!("charlie {}", charlie);

}

TODO: show the error message and explain it on the next page.

Scope For Mutalbe References

NOTE: this can probably be moved to a later part of the book.

This is the earlier example that doesn't work. If we move the println for bravo up it does work.

TODO: There is scope stuff that comes in to play but there's no explicit scope here so I'm guessing rust sees that bravo isn't going to be used anymore after charlie is set so it uses that for scope?

fn main() {

  let mut alfa = String::from("widget");

  let bravo = &mut alfa;
  println!("bravo {}", bravo);

  let charlie = &mut alfa;
  println!("charlie {}", charlie);

}

Look more and the example on this page.

https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html

Mutable And Immutable References Can't Be Combined

TODO: Make this a page:

You can have an immutable reference to a mutable variable.

SOURCE CODE

fn main() {

  let mut alfa = String::from("widget");

  let bravo = &alfa;

  println!("bravo {}", bravo)

}

CODE RUNNER


Testing examples:

Mutable references to immutable variables throw an error. Show this error.

fn main() {

  let alfa = String::from("widget");

  let bravo = &mut alfa;

  println!("bravo {}", bravo);

}

TODO

Things to add to the functions and references.

From: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html


You can make multiple immutable references to a mutable value.

fn main() {
  let mut alfa = String::from("widget");

  let bravo = &alfa;

  let charlie = &alfa;

  println!("bravo {}", bravo);
  println!("charlie {}", charlie);

}

You can't have a mutable reference to a mutable variable at the same time as you already have an immutable one.

fn main() {

  let mut alfa = String::from("widget");

  let bravo = &alfa;

  let charlie = &alfa;

  let delta = &mut alfa;

  println!("alfa {}", alfa);
  println!("bravo {}", bravo);
  println!("charlie {}", charlie);
  println!("delta {}", delta);

}

This was referred earlier I think, but this is confirmation that if rust sees that the values isn't going to be used anymore it releases the reference (TODO: Figure out the actual term for that) and you can use it:

fn main() {

  let mut alfa = String::from("widget");
  println!("alfa {}", alfa);

  let bravo = &alfa;
  println!("bravo {}", bravo);

  let charlie = &alfa;
  println!("charlie {}", charlie);

  let delta = &mut alfa;
  println!("delta {}", delta);

}

You can't make a new variable in a function and then return it. When the function is done, the original value disappears. Rust, prevents this type of thing from happening. If you try to run this code, you'll see the error (TKTKTKT review the error).

fn main() {

  let alfa = widget();

  println!("alfa {}", alfa);
}

fn widget() -> &String {

  let bravo = String::from("hello");

  &bravo

}

This works though.

fn main() {

  let alfa = widget();

  println!("alfa {}", alfa);

}

fn widget() -> String {

  let bravo = String::from("hello");

  bravo

}

Rules of references:

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.

This shows an early return in a function

fn main() {

  let alfa = limit_to_five(3);

  let bravo = limit_to_five(10);

  println!("alfa {} bravo {}", alfa, bravo);

}

fn limit_to_five(value: i32) -> i32 {

  if value > 5 {
    return 5
  }

  value

}

Slices TODO


TODO: I often get wires crossed. Mutability and References are not dependent on each other. Specifically, the idea of an immutable reference throws me. Potentially write something up about that.


from: https://doc.rust-lang.org/book/ch04-03-slices.html

It's labeled as The Slice Type

Baisc example:

fn main() {

  let alfa = String::from("the quick fox");

  let bravo = &alfa[4..9];

  println!("alfa {}", alfa);
  println!("bravo {}", bravo);

}

If you're starting at zero, you don't need to put the zero. These are equivalent.

fn main() {

  let alfa = String::from("the quick fox");

  let bravo   = &alfa[0..3];

  let charlie = &alfa[..3];

  println!("bravo {}", bravo);
  println!("charlie {}", charlie);

}

The same applies for the end.

fn main() {

  let alfa = String::from("the quick fox");

  let bravo   = &alfa[9..13];

  let charlie = &alfa[9..];

  println!("bravo {}", bravo);
  println!("charlie {}", charlie);

}

There's also a note about array slices here: https://doc.rust-lang.org/book/ch04-03-slices.html

It looks like this:

fn main() {

  let alfa = [3, 5, 7, 9];

  let bravo = &alfa[1..3];

  assert_eq!(bravo, &[5, 7]);

}

but maybe wait until vecs.

Also, we haven't seen assertions yet, and I don't totally get the refernce to the stand alone array. or, more to the point, why it's a reference.

This is from the "Other Slices" secition. Need to revisit once there's a better understanding of the parts.

Also, we haven't seen assertions yet, and I don't totally get the refernce to the stand alone array. or, more to the point, why it's a reference.

This is from the "Other Slices" secition. Need to revisit once there's a better understanding of the parts.


Look at: https://doc.rust-lang.org/book/ch04-03-slices.html

For the stuff about literals too and how you can pass them into functions wiht

#![allow(unused)]
fn main() {
fn widget(value: &str) -> &str {}
}

That lets you pass in both &String and &str values.

This is an example where you can pass literal references, and String references, and String slices (which are references by definition)

fn main() {

  let alfa = String::from("the quick fox");
  let bravo = "the lazy dog";

  widget(&alfa);
  widget(&alfa[4..9]);

  widget(&bravo);
  widget(&bravo[4..8]);

}

fn widget(value: &str) {

  println!("Value is {}", value);

}

Question: does this mean that you should pretty much always use &str instead of &String for function parameters?

String literals

Look at the string literals part of the bottom of this page:

https://doc.rust-lang.org/book/ch04-03-slices.html

Tuple Structs

These are like regular structs but they don't have named fields. The values are positional with only the type defined.

struct Widget(i32, f32, bool);

fn main() {

  let alfa = Widget(3, 7.0, true);

  println!("alfa.1 is {}", alfa.1);

}

Tuple Structs are useful for setting a type and passing data to a function without the need for a full struct.

struct Widget (i32, f32, bool);

fn main() {

  let alfa = Widget(3, 7.0, true);

  process_widget(alfa);

}

fn process_widget(widget_input: Widget) {

  println!("Value is {}", widget_input.1)

}

Unit-Like Structs

NOTE: Move this to later after we've discussed the Unit type.

These structs can be used to setup a type that implements traits but holds no data. There will be a later example of that that should be combined with this.

(This will have no output as it is right now)

struct Widget;

fn main() {
  let alfa = Widget;
}

Debug Derived Trait

This is from lower on this page:

https://doc.rust-lang.org/book/ch05-02-example-structs.html

TODO: Make sure you've already talked about the debug print format {:?} and !dbg and how !dbg takes ownership and then returns it and prints to stderr

You can't print a struct directly.

struct Widget { alfa: bool }

fn main() {

  let thing = Widget { alfa: true };

  println!("thing is {}", thing);

}

TODO: Show that error


The {:?} debug syntax won't work either.

struct Widget { alfa: bool }

fn main() {

  let thing = Widget { alfa: true };

  println!("thing is {:?}", thing);

}

TODO: Show the "error[E0277]: Widget doesn't implement Debug" error.


If you add the outter attribute for debugging with: #[derive(Debug)] it'll allow for {:?} or {:#?} to output a basic representation of the struct. (The {:#?}is the same thing as{:?} with a little nicer formatting and is that's shown here)

#[derive(Debug)]
struct Widget { alfa: bool }

fn main() {

  let thing = Widget { alfa: true };

  println!("thing is {:#?}", thing);

}

Using !dbg

Probably you should just show this for the debuggin stuff instead of {:?} in println!() for the default way to do debugging. TODO: look into the differences to see about that, but almost certianly move the {:?} to a later section.

NOTE: this isn't showing up on the playground possibly because only stdout and not stderr is returned? Need to look into that.

#[derive(Debug)]
struct Widget { alfa: bool }

fn main() {

  let thing = Widget { alfa: true };

  dbg!(thing);

}

Use dbg! Around Expressions

You can use around expressions. This means you can do things like this (TODO: figure out if dbg! can output on the rust playground.)

fn main() {

  let alfa = 3;
  let bravo = 5;

  let charlie = dbg!(alfa * bravo);

  println!("charlie is {}", charlie);

}

match

The enum type lets us define a set of available options and ensure that it must contain one of them and that it can't contain more than one. (That is, it contains exactly one)

match expressions provide a way to figure out which one of those options the enum contains and to do something based on that. For example, here's an enum with two empty options called Alfa and Bravo.

enum Widget {
  Alfa,
  Bravo
}

We can create a variable with an enum that's using Bravo like this:

let thing = Widget::Bravo;

We then use match to check thing

match thing {

}

The check is done with arms which are all the different options from the enum type. In our case, that's Alfa and Bravo. Because we're accessing them through the variable, we have to include the Widget name of the enum as well which gives us:

Widget::Alfa

And

Widget::Bravo

So, we use those two items as the things to match on. For each one we have a => that points to a code block to run that's contained in the {} curly brackets.

enum Widget {
  Alfa,
  Bravo
}

fn main() {
  let thing = Widget::Bravo;

  match thing {
    Widget::Alfa => {
      println!("it's alfa");
    }
    Widget::Bravo => {
      println!("it's bravo");
    }
  }
}

Step By Step

Passing Values

The options in enums can hold data too.

This is how to get at that.

enum Widget {
  Alfa(String),
  Bravo
}

fn main() {
  let token = String::from("apple");
  let thing = Widget::Alfa(token);

  match thing {
    Widget::Alfa(value) => {
      println!("alfa with {value}");
    }
    Widget::Bravo => {
      println!("bravo by itself");
    }
  }
}

match


Example from the book (with some more code so it actually does something)

enum Coin {
  Penny,
  Nickel,
  Dime,
  Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
  match coin {
    Coin::Penny => 1,
    Coin::Nickel => 5,
    Coin::Dime => 10,
    Coin::Quarter => 25,
  }
}

fn main() {

  let my_coin = Coin::Nickel;

  println!("I have {} cents", value_in_cents(my_coin));

}

Make a note that you can put {} for the code blocks, but it's usually not done if it fits on one line.


This is a basic way to use match to do something based off the type of enum that got created.

enum Widget {
  Alfa,
  Bravo
}

fn display_variant(value: Widget) {
  match value {

    Widget::Alfa => {
      println!("kind is Widget::Alfa");
    },

    Widget::Bravo => {
      println!("kind is Widget::Bravo");
    }

  }
}


fn main() {

  let thing1 = Widget::Alfa;
  display_variant(thing1);

  let thing2 = Widget::Bravo;
  display_variant(thing2);

}

Here's anothe example where you pull values out and bind them so you can use them. This is one way to get data out of an enum. (I think if let is another way. Not sure if there are other ways.)

enum Widget {
  Alfa,
  Bravo(String),
  Charlie { value: i32 },
}

fn show_report(item: Widget) {
  match item {
    Widget::Alfa => {
      println!("It's Alfa");
    },
    Widget::Bravo(text) => {
      println!("It's Bravo with {}", text);
    },
    Widget::Charlie { value } => {
      println!("It's Charlie with {}", value);
    },
  }
}

fn main() {
  let item1 = Widget::Alfa;
  show_report(item1);

  let item2 = Widget::Bravo(String::from("hello"));
  show_report(item2);

  let item3 = Widget::Charlie { value: 7 };
  show_report(item3);

}

Scope

Scope example that works, this we'll see it break on the next page.

SOURCE CODE

fn main() {

  let alfa = 1;

  {
    let bravo = 2;
    println!("bravo {}", bravo);
  }

  println!("alfa {}", alfa);

}

CODE RUNNER

Scope Error

Intentional error. Look at the message on the next page.

SOURCE CODE

fn main() {

  let alfa = 1;

  {
    let bravo = 2;
    println!("bravo {}", bravo);
  }

  println!("alfa {} - bravo {}", alfa, bravo);

}

CODE RUNNER

Scope Error Message

TKTKTK

   Compiling playground v0.0.1 (/playground)
error[E0425]: cannot find value `bravo` in this scope
  --> src/main.rs:10:40
   |
10 |   println!("alfa {} - bravo {}", alfa, bravo);
   |                                        ^^^^^ not found in this scope

For more information about this error, try `rustc --explain E0425`.
error: could not compile `playground` due to previous error

Guildlines

These are not hard rules. They are guidelines. Staying from them when it makes sense makes total sense.

  • Get to code that actually does something as fast as possible

  • Make the initial explinations as short as possible

  • Focus on how not why at the start.

  • Maybe: cycle through concepts (e.g varaibles with numbers, then if/then, then variables with booleans, then if/else., the variables with Strings, then if/else if/else)

  • You don't have to explain everything all at once. (e.g. you don't have to explain floats in order to talk about integers and you don't have to talk about the bits an integer can take up or the signed vs unsigned versions when you first start.)

  • Run off the idea that folks will go through the entire book. So, you don't need to explain everything all at once. (see also the other note about that)

  • Only show one way to do something (e.g. with ranges instead of showing (1..10) and then explaining that if you want to actually get to ten, just show (1..=10).

  • Don't worry too much about explaing the syntax. That is: Show. Don't Tell.

  • Avoid talking about what you'll show later. Wait until you get there. (This is a continuation of show don't tell)

  • Avoid statements like "which we haven't talked about yet". If the person thinks about it at all, they'll know that's the case so there's littl point in mentioning it.

  • Don't dig into every possible option at the start with edge cases. e.g. "In Rust, the main() function is used to kick things off" works find without having to. "In most programs, except for these types when its... whatever") It's like taking an 80/20 thing where the statement needs to be accurate but not all inclusive. Details of the different cases can be dealt with later.

  • You can also use lanage like "basic variables are defined like" instead of "variables are defined like".

Work In Progress

This site is a work in progress. Everything prior to this page is in pretty good shape for a first iteration.

The pages that follow contain drafts, and scratch notes and aren't in order. They'll shift around and solidify. You're welcome to check them out, but please keep that in mind.

TODO List

Stream

  • [] Read a file to string

  • [] Look at nom for parsing

  • [] Add button to disable live typo detection.

  • Add two buttons into the Code Runner that let you click them to decide to Type or to decide to Copy and Run the code directly.

  • Move the run button below the Code Runners

  • Add spacing to the code lines so you don't have to put spacer lines between all of them.

  • Add a button to copy the text automatically, maybe you could make that the thing where when you click in you click inside for copy the text or type the text. (because you have to click in anyway unless you tab in so you should handle tabbing in too)

  • Add styles to txt code fences.

  • Turn off whatever is overriding the default scroll bars.

  • See if you can turn off whatever is making the TOC sidebar nav jump to center the items so that it stays with where you had it. (Probably requrie setting a cookie and reading it?)

Other

  • [] Show how to read this type of thing from the docs pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir>

  • [] Show a match expression that returns a value assuming that's possible.

  • Verify the font styling in mutable-variables/index.html works cross browser

  • Add section about how to read the docs

  • Verify that the generic term statement can be used to describe expressions (e.g. "an if statement is an expression")

  • Is it possible to make the book dynamic in some way, even if it's just manually. Like a setting where you can select if you've got any programming experience or not and if not you get extra text and maybe some variations in the main text.

  • What if there was a spaced repetition thing built into the site with programs to run that helped work on the parts you weren't as sure about.

  • Put in fun things to do and see for the different parts that you go through as options?

  • What if there were different versions of the book with different exmample variable names (e.g. if you want to have animals, or colors, or nato alphabet) Or, maybe not different versions, but like settings so you could control things.

  • Maybe don't use anything that's stored on the stack for the first part of the book? Introduce String and Vec instead of string literals and arrays. That way you don't have to talk about what has to be borrowed and what doesn't require it.

  • Add note about how things can move to differnt lines and still be the same expression or statement.

  • Add something so if you hit the run button when there's nothing in the code running it asks you if you want to type stuff in or copy it directly?

  • Add a button or link to copy the source code into the code runner directly. (do that in addtion to the run check to make sure there's code in the code editor)

  • Make an early example of doing 2+2 in the variables chapter.

  • Figure out where to put Statements and Expressions.

  • Scope

  • Comments

  • Rust does not try to convert non-boolean types to booleans

  • String literals

  • Come up with some example programs to use as full examples at the end of each section.

  • Talk about the Rust Prelude: https://doc.rust-lang.org/std/prelude/index.html

  • Talkd about the Rust editions

  • Talk about the &str slice type.

  • Talk about derived traits.

  • Talk about how structs and enums are keys to stuff.


Put in a note about if you get this error it probably means you didn't put code in the code runner:

#![allow(unused)]
fn main() {
   Compiling playground v0.0.1 (/playground)
error[E0601]: `main` function not found in crate `playground`
  |
  = note: consider adding a `main` function to `src/main.rs`

For more information about this error, try `rustc --explain E0601`.
error: could not compile `playground` due to previous error
}

Comman error:

Compiling playground v0.0.1 (/playground) error[E0615]: attempted to take value of method do_output on type Widget --> src/main.rs:15:9 | 15 | thing.do_output; | ^^^^^^^^^ method, not a field | help: use parentheses to call the method | 15 | thing.do_output(); | ++

For more information about this error, try rustc --explain E0615. error: could not compile playground due to previous error


Using () instead of {}

Compiling playground v0.0.1 (/playground) error: expected identifier, found keyword true --> src/main.rs:16:28 | 16 | let thing = Widget(alfa: true); | ^^^^ expected identifier, found keyword | help: escape true to use it as an identifier | 16 | let thing = Widget(alfa: r#true); | ++

error: invalid struct delimiters or fn call arguments --> src/main.rs:16:15 | 16 | let thing = Widget(alfa: true); | ^^^^^^^^^^^^^^^^^^ | help: if Widget is a struct, use braces as delimiters | 16 | let thing = Widget { alfa: true }; | ~ ~ help: if Widget is a function, use the arguments directly | 16 - let thing = Widget(alfa: true); 16 + let thing = Widget(true); |

error[E0070]: invalid left-hand side of assignment --> src/main.rs:8:16 | 8 | &self.alfa = false | ---------- ^ | | | cannot assign to this expression

For more information about this error, try rustc --explain E0070. error: could not compile playground due to 3 previous errors

Statements and Expressions

From: https://doc.rust-lang.org/book/ch03-03-how-functions-work.html

Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resultant value. Let’s look at some examples.

Constants

There's another type of variable besides mutable and immutable. They're called "constants".

Constants are like immutable variables with a few extra criteria. We've covered enough to get the idea for the first three:

  1. They are defined using the const keyword instead of let
  2. The mut keyword can't be used when creating them
  3. Names should always be in UPPER_SNAKE_CASE by convention.

These next three criteria require some knowledge we haven't gotten to yet, We'll touch on them in the next few pages.

  1. Constants can be declared in global scope (which will see on the next page)
  2. They must have a type annotation (e.g. the i32 in the example below which is coming up).
  3. They must be set to something determined at compile time (which we'll also get to shortly)
fn main() {
  const ALFA: i32 = 100;
  println!("Alfa {ALFA}");
}

CODE RUNNER


TODO

  • Examine moving constants to a points after you've talked about scope and types. Probably that makes the most sense.

Constants In The Global Scope

Here's the previous example we used to demonstrate a constant.

fn main() {
  const ALFA: i32 = 100;
  println!("Alfa {ALFA}");
}

The const and println!() statements are between the { and } of the main() function. The terminology used for this is to say those statements are in the main() function's "scope".

Scope is like a one-way wrapper where:

  1. Anything that's in a surround scope can be be accessed by what's inside the inner scope, but
  2. Anything inside the scope can't be accessed by anything outside of it.

The main() function's scope is clearly delimited by the { and }. There's another scope surrounding it in our program that's invisible. It's what's called the "global" scope. It begins at the start of the source code and goes all the way to the end encompassing everything in between.

What this means is that we can move the assignment for our ALFA constant above the definition of the main() function like this:

SOURCE CODE

const ALFA: i32 = 100;

fn main() {
  println!("Alfa {ALFA}");
}

CODE RUNNER

Types Required

TODO: Fill out this page with notes on types being required.

TBD on the content based of if this gets moved to after types have been covered or if it stays before them.

Compile Time

TODO: Fill out this page with notes on how stuff need to be defined in a way that it can be determined at compile time.

Shadowing Variables

Immutable Variables Can't Be Changed

Trying to update the value of an immutable variable results in an error. This code sets an initial immutable value with let alfa = 3 and then tries to update it with alfa = 5 which causes an error.

Click Run to see an example of what that looks like.

#[allow(unused_variables)]
#[allow(unused_assignments)]
fn main() {
  let alfa = 3;
  alfa = 5;
}

Shadowing Variables Works

While you can't update an immutable variable, you can define a new one with the same name. This is called shadowing.

Enter and run this code for an example

fn main() {
  let alfa = 3;
  println!("The value is {alfa}");

  let alfa = 5;
  println!("The value is {alfa}");
}

CODE EDITOR

TODO

  • Come up with examples of when you might want to shadow a variable (changing type might be one, but I don't know yet why you would do that instead of making a new variable)

Shadowing Variables In Scope

Shadowing a variable only applies to the scope that the shadowing happens in. If you bind a variable in one scope, then shadow it in a child scope the new version of the variable will be used in that scope. For example:

fn main() {
  let alfa = 3;
  println!("The value is {alfa}");

  {
    let alfa = 5;
    println!("The value is {alfa}");
  }

}
The value is 3
The value is 5

If you then exist the child scope and use the variable name in the original scope it returns to the original version.

fn main() {
  let alfa = 3;
  println!("The value is {alfa}");

  {
    let alfa = 5;
    println!("The value is {alfa}");
  }

  println!("The value is {alfa}");
}

Enter this program and run it to see the output.

CODE EDITOR

Shadowing Variables To Change Type

Shadowing variables allows you to change their type. That's something you can't do with a regular mutable variable (i.e. one defined with let mut). For example, this code tries to change from a string to a number. Run it and you'll see the error message.

fn main() {
  let mut alfa = "example";
  alfa = 7;
}

Using shadowed variables to change the type in a way that works looks like this:

fn main() {
  let alfa = "example";
  let alfa = 7;
  println!("The value is {alfa}");
}

CODE EDITOR

Characters

NOTE: This was originally right after booleans in the early Data Types chapter. I'm moving it out for now because I rarely see char used independently. We'll address in a later section.


The char type in Rust holds a single character. Variables of the char type are defined with single quotes. They can be set implicitly like this:

let alfa = 'a';

Or explicitly like this:

let bravo: char = 'b';

Here's the explicit version in a full program:

SOURCE CODE

fn main () {
  let charlie: char = 'c';
  println!("Value {charlie}");
}

CODE RUNNER

Destructuring Tuples

https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type

Adding Values To Tuples

Arrays


NOTE: Arrays are covered in chapter 3 of The Rust Book. I'm putting them later based off the sentence "If you're unsure whether to use an array or a vector, chances are you should use a vector.


Arrays are like tuples. They're containers that hold a collection of values. Arrays are different from tuples in two ways:

  1. Every value the contain must be of the same type.

  2. The number of items in an array is set when the array is created and cannot be changed.

Arrays are created with square brackets. The format the defines them is the type followed by a semicolon then the number of items in the array. The format looks like this for an array of three integers.

let alfa: [i32, 3] = [3, 5, 7];

Accessing the elements of an array is done using the name of the array followed by the desired values index number surrounded by square brackets.

println!("Value {}", alfa[0]);

Putting those parts together we get

SOURCE CODE

fn main() {

  let alfa: [i32; 3] = [3, 5, 7];

  println!("1st {}", alfa[0]);
  println!("2nd {}", alfa[1]);
  println!("3rd {}", alfa[2]);

}

CODE RUNNER

Statements And Expressions

The widget function in the last example looked like this:

fn widget() -> i32 {
  5 + 5
}

I bring this up to point out that there is no ; after the 5 + 5. This is different than all the other lines we've seen in functions so far. For example:

println!("Hello, World");

The reason for this difference is because Rust functions are made up of two types of things: Statements and Expressions.

From The Rust Book:

  • Statements are instructions that perform some action and do not return a value.
  • Expressions evaluate to a resultant value.

Said another way, expressions give you something back. Statements don't.

Option Type

#![allow(unused)]


fn main() {
}

Options are returned by lots of things to let you know if there is a valid value or not.

More stuff on this page:

https://doc.rust-lang.org/book/ch06-02-match.html

Prerequisets

This site assumes you know the following:

  • What immutable means.
  • What it means to assign a variable.
  • What keywords in a language are

TODO

  • Provide links for how to learn all the above.

About The Site

DRAFT

I started learning Rust with The Rust Book. It was too much for me. There's too much explanation and not enough code for the way I learn.

Short examples work better for me. Just a few lines of code with no comments intertwined.

Documenting what I'm learning is another big part of how I figure things out. This site combines both things. It's a collection of refined examples based off The Rust Book and Rust By Example with my write ups.

I'm publishing it to help other folks who learn the way I do.

NOTES

  • This is a work in progress. It's not even a full first draft yet.
  • I'm filling out that pages, but they aren't in order yet.
  • Most examples have a CODE EDITOR under them where you can type the code and run it. That's been very helpful for me.
  • I've suppressed some warnings that would require making examples more complicated. None of them effect functionality and we'll cover them specifically in their own chapter.

Terminology

Terms to define

  • argument
  • assign
  • bind
  • compiled
  • execute
  • function
  • keyword
  • main()
  • parameter
  • run
  • statement

Typo Test

fn main() {

  println!("alfa");

}

Work In Progress

This site is a build-in-public project. It's still a very early work in progress.

I'm working to make sure everything is accurate, but there's lots of stuff that's out of order and more stuff missing.

All that's to say, it's not ready to go yet, but feel free to poke around.


Other notes to drop in.

  • My source materials are The Rust Programming Language (aka The Rust Book), Rust By Example, Rustlings, and Rust Adventure. I'm pulling what I learn from all those places and making the examples you see here

  • Reading this site like a book without typing is perfectly acceptable. But, I think you'll get the most out of it by actually typing in the examples (and making all the typos that includes)

  • You don't need to have Rust installed to use the site. All the examples are compiled and run directly in the browser via The Rust Playground

Turning Off Warnings

This code produce the two warnings and one error on the previous page.

fn main() {
  let alfa = 7;
  alfa = 9;
}

All we care about right now is the error message. Rust provides as way to suppress warnings (TODO: find the name of these things, "directives", maybe?)

There are several directives available. (TODO: link to the other directives). The one we're
interested in right now is #[allow(unused)]

(TODO: Update based on the one with the ! as well and talk about the difference i.e. #![allow(unused)]

It's applied like this. Give it a shot to see the error message.

SOURCE

#[allow(unused)]
fn main() {
  let alfa = 7;
  alfa = 9;
}

CODE RUNNER

The Error Message

Here's the error message

   Compiling playground v0.0.1 (/playground)
error[E0384]: cannot assign twice to immutable variable `alfa`
 --> src/main.rs:4:3
  |
3 |   let alfa = 7;
  |       ----
  |       |
  |       first assignment to `alfa`
  |       help: consider making this binding mutable: `mut alfa`
4 |   alfa = 9;
  |   ^^^^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `playground` due to previous error

For now, the two lines we need to look at are:

error[E0384]: cannot assign twice to immutable variable `alfa`

and

|       help: consider making this binding mutable: `mut alfa`

Error Messages

TODO: Make a collection of error message showing some of the ways things can go wrong.

Examples to use:

  • Not sending a value to a function
  • Not putting a ; after a line in a function that needs it.

See if there's a list of the most common errors folks hit.

Variables - Origian with types

This is the first version of the Variables page that included type defintions. Moving to a version that doesn't have that for now to see how it works

Keeping this here for reference.


Variables in Rust are created using the following structure:

  1. The let keyword
  2. A name for the variable (e.g. alfa)
  3. A : that acts as a separator
  4. A data type
  5. The = sign
  6. The value to bind to it (e.g. 7)
  7. A ; the ends the definition

The data type from item 4 tells rust what kind of content the variable can hold. For example, it might be a number, or a letter, or full sentence. We'll dig into data types in the next chapter. We'll use i32 (which stands for a number) until we get there.

Putting it all together we get this:

let alfa: i32 = 7;

Using that line in we can create a full program that defines the alfa variable then prints it out.

SOURCE CODE

fn main() {

  let alfa: i32 = 7;

  println!("The value is {}", alfa);

}

CODE RUNNER

if let

TODO: Do this after match

SOURCE CODE

fn main() {
}

CODE RUNNER

Loops

Moving this back in the stack since while and for loops are much more common

SOURCE CODE

fn main() {

  let mut alfa = 1;

  loop {
    println!("alfa is {}", alfa);

    if alfa == 10 {
      break;
    }

    alfa += 1;
  }

}

CODE RUNNER

Binding Values

SOURCE CODE

fn main() {
  let mut alfa = 1;

  let bravo = loop {
    println!("alfa is {}", alfa);

    if alfa == 10 {
      break alfa;
    }

    alfa += 1;
  };

  println!("bravo is {}", bravo);

}

CODE RUNNER

Labels

You can add labels like this (not useful yet, but we'll see where they become helpful on the next page)

SOURCE CODE

fn main() {
  let mut alfa = 1;

  'outter_loop: loop {

    println!("alfa is {}", alfa);

    if alfa >= 10 {
      break 'outter_loop;
    }

    alfa += 1;
  }
}

CODE RUNNER

Nested Loops

Nested loops (one inside of another one) are where labels become useful.

SOURCE CODE

fn main() {
  let mut alfa = 1;

  'outter_loop: loop {
    let mut bravo = 1;

    'inner_loop: loop {

      if bravo == 4 {
        break 'inner_loop;
      }

      if alfa == 5 {
        break 'outter_loop;
      }

      println!("alfa {} bravo {}", alfa, bravo);

      bravo += 1;

    }

    alfa += 1;
  }
}

CODE RUNNER

Enum Type Aliases

NOTE: This is the first type aliases things that showed up in Rust by example. Not sure if it should be moved to a generic type aliases page or not yet.

Aliase can be used if you've got a long name and want to make it easier to use.

NOTE: This doesn't produce any output

enum ThisHasLotsOfLettersAndIsTooLong {
  Alfa,
  Bravo,
}

type Shorter = ThisHasLotsOfLettersAndIsTooLong;

fn main() {

  let thing = Shorter::Alfa;

  match thing {
    Shorter::Alfa => println!("Got Alfa"),
    Shorter::Bravo => println!("Got Bravo"),
  }

}

Shorter names can be done in impl blocks. This happens automatically with Self (which was mentioned before. TBD on if you need this here too, but probably works to make it explict if you reference the earlier stuff with structs or whatever it was)

(Of course, this is only showing the change inside the impl. Guess you'd want to do type as well for the other one.)

enum ThisHasLotsOfLettersAndIsTooLong {
  Add,
  Subtract,
}

impl ThisHasLotsOfLettersAndIsTooLong {
  fn run(&self, x: i32, y: i32) -> i32 {
    match self {
      Self::Add => x + y,
      Self::Subtract => x - y,
    }
  }
}

fn main() {

  let alfa = ThisHasLotsOfLettersAndIsTooLong::Add;
  println!("Addition {}", alfa.run(3, 4));

  let bravo = ThisHasLotsOfLettersAndIsTooLong::Subtract;
  println!("Subtraction {}", bravo.run(4, 3));

}

Fizz Buzz

Not sure I'm goint to put Fizz Buzz in here or not, but one idea might be to do different versions of it showing different approaches.

SOURCE CODE

fn main() {
  for count in 1..=20 {
    if count % 3 == 0 {
      if count % 5 == 0 {
        println!("FizzBuzz");
      } else {
        println!("Fizz");
      }
    } else if count % 5 == 0 {
      println!("Buzz");
    } else {
      println!("{}", count);
    }
  }
}

CODE RUNNER

Hello, World - The Parts

Let's take a look at the individual parts that make up our "Hello, World" program.

fn main() {
  println!("Hello, World");
}
  • fn is short for function which is what we're defining.

  • main is the name we're assigning to the function. In Rust, the main function is used to kick things off.

    Every rust program must have a main function. It's the entry point that kicks things off.

  • The () at the end of main() is where we define arguments that can be passed to the function. They're empty here which means main() doesn't accept any.

  • The opening { and } curly bracket set the starting and ending points for the function's code block.

  • println!("Hello, World"); is what prints out the text.

We'll dig into more details about each of those elements throughout the site.

A Complete Program

Let's look at the "Hello, World" code again:

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

Those three lines of code are a complete program. It may be simple, but when you've typed them in you've officially written Rust.