Ok lets start coding.

enough of this talking biz, its coding time. I have always been under the impression its easier to learn with my hands than my eyes.





















To begin with, There are 2 things that have to be understood in Rust

This is for fundamental understanding of the language.

  1. Iterators
  2. Enums

Once you get these two, it becomes easier to work with rust initially. As these concepts are a bit wonkey coming from TypeScript.


You are even probably thinking... "I use iterators all the time and enums are horrible!"


From a typescript perspective you are right and from a rust perspective, you are wrong.





















Lets start with iterators

(first basic whiteboard explanation)





















Iterators

I think iterators will make the easiest transition as they have the strongest similarity in javascript.

and we can start with .map





















Quick example in TypeScript

Lets go over a quick example in typescript

Lets create a script that:

  • creates an list initialized with 1, 2, 3
  • adds 1 to each item in that list
  • prints the list




















// what happens here?
const foo = [1, 2, 3].map((x) => x + 1);
console.log(foo);




















Quick example in Rustlang

Now lets do it in rust!

  • (in case you forgot) a closure is defined
// defining a closure
|x| {
    //... body with a return
    return x * 5;
}

// without body
|x| x * 5

See how far you can get on your own!

  • do you remember how to define a vector? vec![...]
  • to iterator over references .iter()
  • map it
  • but... how to get an iterator back to a vector... (collect)

(in case you forgot)

  • creates an list filled with 1, 2, 3
  • adds 1 to each item in that list
  • prints the list
    • debug print works on vectors automagically println!("{:?}", foo);




















fn main() {
    let items: Vec<isize> = vec![1, 2, 3]
        .iter() // create the iterator to go over the elements in teh array
        .map(|x| x + 1) // do the plus one'ings
        .collect(); // take the iterator and put it somewhere..

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




















What is collect?

One thing that is different than you may be use to is that an Iterator is its own data type. So we must convert from an iterator back into the struct we want and in our case its a Vec

So lets do this manually





















Complete Code

fn main() {
    let items = vec![1, 2, 3];
    let mut iter = items
        .iter()
        .map(|x| x + 1);

    let mut collected_items = vec![];
    while let Some(value) = iter.next() {
        collected_items.push(value);
    }

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

Its sometimes easy to think things magic when they are not, its literally, in our example, a simple while loop





















Wanna see something cool with collect?

Well, collect is more that just "put back into a vector"

(show them the deets, String, HashSet, HashMap)





















Complete Code

Collect into string!

let foo: String = vec!["this", "is", "a", "test"]
    .into_iter() // what the heck is this?  we will talk more about this
    .collect();




Collect into HashSet (this would be Set in JS)

let foo: HashSet<isize> = vec![1, 2, 3]
    .into_iter()
    .collect();




Collect into a HashMap

let foo: HashMap<&str, usize> = vec!["this", "is", "a", "test"]
    .into_iter()
    .enumerate() // Adds the index to the iterator!
                 // one of the glories of rust is that we work with iterators
    .map(|(idx, item)| (item, idx)) // reverses the order
    .collect();

map(|(idx, item)| is an example of destructuring.





















We are going to play a game

this will help you see whats possible





















What is value?

let value: usize = vec![1, 2, 3]
    .iter()
    .sum();




















let how_many_items: usize = vec![1, 2, 3]
    .iter()
    .skip(2)
    .count();




















What will i print?

    vec![1, 2, 5, 9, 4]
        .iter()
        .skip(2)
        .take_while(|&&x| x > 4) // i can explain the && later,
                                 // but know its pattern matching
        .for_each(|x| println!("{}", x));




















let what_about_this: usize = vec![1, 2, 3]
    .iter()
    .filter(|x| *x % 2 == 0) // we will explain the * later
    .count();




















Iterators from other collections!

let map = HashMap::from([
   ("foo", 1),
   ("bar", 2),
   ("baz", 3),
]);

map
    .iter()
    .for_each(|(k, v)| println!("{}: {}", k, v));




let set = HashSet::from([
    "foo",
    "bar",
    "baz",
]);

set
    .iter()
    .for_each(|v| println!("{}", v));




You can even create your own iterators!

We will soon, but here is a basic example!

let todos = Todo { ... values ... }

for task in &todos { // requires trait implementations
    println!("I need to do: {}", task); // require trait implementations
}




















Iterator way of thinking

This is an important concept which isn't in javascript

[Type] -> [Iterator] -> [Type]

This typically gives us code that looks like.

some_type
    .iter() // creates iterator
    .filter(|x| ...

    ) // A series of combinators

    .collect/sum/count/for_each() // some operation that takes the iterator and consumes it




















Lets do a simple exercise

Lets do the following.

  1. create this file called project/lines
hello
fem
how
1
2
3
are
you?
  1. read file lines
  2. print out each line individually

TypeScript

I'll give you a few moments to try this yourself





















Complete Code

import fs from "fs";

const file = fs.readFileSync("lines");

file
  .toString()
  .split("\n")
  .forEach((line) => console.log(line));




















Lets do the same in Rust

Since you are new, i'll have to walk through each line of code.

Just in case you forgot

  1. read a file from disk
  2. print out each line individually




















Complete Code


fn main() {
    let file = std::fs::read_to_string("lines").unwrap();

    file
        .lines()
        .for_each(|line| println!("{}", line));
}




















How about every other line?

Add a few more lines to your test file and then implement it in TypeScript

I'll give you ~1 minute to do this





















Complete Code

import fs from "fs";

const file = fs.readFileSync("lines");

file
  .toString()
  .split("\n")
  .filter((_, i) => i % 2 === 0)
  .forEach((line) => console.log(line));




















But how to do this in rust?

You have seen me mention .enumerate() .filter(|x| ...) thus far, why not take 1 minute and see if you can update your code to take every other!





















Complete Code

Observation: Rust does exactly what you tell it and no more.

fn main() {
    let file = std::fs::read_to_string("lines").unwrap();

    file
        .lines()
        .enumerate()
        .filter(|(idx, _)| idx % 2 == 0)
        .for_each(|line| println!("{}", line.1));
}




















One more

do these steps IN ORDER.

  • every other line
  • skip the first two lines
  • print two lines




















Complete Code

import fs from "fs";

const file = fs.readFileSync("lines");

file
  .toString()
  .split("\n")
  .filter((_, i) => i % 2 === 0)
  .filter((_, i) => i >= 2 && i < 4)
  .forEach((line) => console.log(line));




















Now Rust

Remember when i said rust has an amazing combinator set? Its time to shine

i think you should give it a try





















Complete Code


fn main() {
    let file = std::fs::read_to_string("lines").unwrap();

    file
        .lines()
        .enumerate()
        .filter(|(idx, _)| idx % 2 == 0)
        .skip(2)
        .take(2)
        .for_each(|line| println!("{}", line.1));
}




















Lets break down what happened

split("\n")
  .filter((_, i) => i % 2 === 0)
  .filter((_, i) => i >= 2 && i < 4)
  .forEach((line) => console.log(line));




















Split

that takes substrings and creates an array.

That means calling split iterates the entire string up front and creates a list

[
    "line1",
    "line2",
    ...
]




















What about filter?

Filter takes in a list and produces a new list

[
    "line1",
    "line2",
    ...
] => [
    "line2",
    "line4",
    ...
]




















The second filter

[
    "line1",
    "line2",
    ...
] => [
    "line2",
    "line4",
    ...
] => [ // no matter how many lines were before, it goes through ALL
    "line6",
    "line8",
]




















for each

This just goes through each item in the final array, i approve





















So what does the "code produced" ackshually look like?

With javascript its so easy to perform many high level tasks that you forget exactly what is happening.

Here is "transpiled" code

function filter_1(x: number): boolean {
  return x % 2 === 0;
}

function filter_2(x: number): number {
  return x >= 2 && x < 4;
}

// Skipping the split operation
let a = contents.toString().split("\n");
let b = [];
for (let i = 0; i < a.length; ++i) {
  if (filter_1(a[i])) {
    b.push(a[i]);
  }
}
let c = [];
for (let i = 0; i < b.length; ++i) {
  if (filter_2(i)) {
    c.push(b[i]);
  }
}
for (let i = 0; i < c.length; ++i) {
  console.log(c[i]);
}

v8 may optimize some of this away. To what extent, i don't have the faintest clue and neither do you





















Same example, but in rust

    .lines()
    .enumerate()
    .filter(|(idx, _)| idx % 2 == 0)
    .skip(2)
    .take(2)
    .for_each(|line| println!("{}", line.1));

// Goes through every char
let mut start = 0;
let mut taken = 0;
let mut skipped = 0;
let mut lines_found = 0;
for (idx, c) in lines.enumerate().chars() {
    if c !== "\n" {
        continue;
    }

    // doesn't copy, just a &str (ptr, len)
    let slice = lines[start..idx];
    start = idx + 1;

    lines_found += 1
    if lines_found % 2 == 0 {
        continue
    }

    if skipped < 2 {
        skipped += 1;
        continue;
    }

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

    if taken == 2 {
        break;
    }
}




















Zero cost abstractions

You will see this phrase commonly in the rust community, and this is why. Its able to have these higher order abstractions, just without all the cost of them





















Questions?