Option

Option is the answer to typescript's undefined / null problem.

Option is an enum



The thing about null / undefined is that you get different answers for why you should use what or the other...

null: undefined on purpose

undefined: we have no reasonable guarantee it will be there...

















Fun Challenge

is this valid TS?

type Foo = {
  bar?: string;
};

const item: Foo = {};
const item2: Foo = { bar: "" };
const item3: Foo = { bar: undefined }; // <-- is this valid?
















Depends on your TSConfig

exactOptionalPropertyTypes requires that bar either be not specified or a string.


Why is item3 not technically valid


type Foo = {
  bar?: string;
};

const item3: Foo = { bar: undefined };
















Rust handles it different and better

At first, this may seem annoying, but once you get use to them, it grows on you a ton

  1. the ENUM definition of Option
enum Option<T> { // yes, generics can be used in enums, again, cool
    None
    Some(T),
}

That means we can create Options with any inner value.

  1. Rust also recognizes that they will be used so much that they made them first class citizens.
// notice i don't have to do Option::Some(...)
let foo = Some(5);
let foo = Some("different type");
let foo = Some(Custom { age: 69, name: "ThePrimeagen" });
















But why?

Why do we need Options in rust? The answer is memory. If you might or might not return an item from a function, rust needs to be able to allocate that memory on the stack. (we will talk about this more shortly)

This happens in JS too, it is just behind the scenes in the engine

















Working with Option

They are enums, so match/if let pattern matching works, but there is more because there are plenty of convenient methods.

















Lets start with a small exercise

You can erase all the code you have written thus far and lets start with TypeScript.

I want you to write a function with the following:

  • signature takes in a number or undefined and returns a number
  • if the value is undefined, return 0
  • else multiply the value by 5
  • the signature cannot use ? as that doesn't mean you have to pass in undefined.

I think you can do this pretty quick, i'll give you one minute.

















Complete Code

function multiply(num: number | undefined): number {
  return (num ?? 0) * 5;
}
















Lets do the same thing with rust

Remember, repetition and small exercises are a great way to learn, so make sure you are participating!

Requirements

  • signature takes in a number or undefined and returns a number
  • if the value is undefined, return 0
  • else multiply the value by 5

I'll give you a couple moments to try yourself (don't just code, scroll down (note to me))

















How I started

When you first start with rust, it looks pretty ugly, so let me show you how i would have completed this.

















Complete Code (How I would have)

fn multiply(num: Option<usize>) -> usize {
    if num.is_some() {
        return num.unwrap() * 5; // unwrap a None causes a panic
    }
    return 0;
}
















How I would now

















Much nicer

fn multiply(num: Option<usize>) -> usize {
    return num.unwrap_or(0) * 5;
}
















Lets change up the rules a bit

Start with TypeScript
instead of returing 0, if undefined is provided, return undefined else multiply by 5

















Complete Code

function multiply(num: number | undefined): undefined | number {
  return num === undefined ? undefined : num * 5;
}
















Can Rust help us?

yes it can! We ackshually have 2 different ways we can accomplish this

















Method 1: map

Option#map keeps the box value (Option) while giving you a chance to deal with the inner value, which for us is a number.

let foo = Some(5);
//-----------------v is a {integer}!
let foo = foo.map(|x| {...})
//---^ is now Option<Return type of map>

Go a head, give it a shot, and upgrade our previous example. The new signature should look like fn multiply(num: Option<usize>) -> Option<usize> {...


i'll give you a moment to try it out

















Complete Code

fn multiply(num: Option<usize>) -> Option<usize> {
    return num.map(|x| x * 5); // remains as an option
}
















Version 2, ? operator

When you have a function that returns an Option you can automagically unwrap the value.


That might sound confusing, let me show you what its doing

fn test() -> Option<usize> {
    let foo = Some(5);

    // --------v Option<usize>
    let foo = foo?;
    //---^ usize
}

This expands out to

fn test() -> Option<usize> {
    let foo = Some(5);
    let foo = match foo {
        Some(x) => x,
        None => return None
    };
}

See if you can change your .map example to use ?

















Complete Code

fn multiply(num: Option<usize>) -> Option<usize> {
    return Some(num? * 5);
}
















Lets do a little exercise

lets do a moment of practice!


Small program

  • write a function, call it practice, that takes in nums of Vec<usize> and an index: usize

  • Return one of the following

    • if value exists at index in nums, return it multiplied by 5
    • if there is no value, return index multiplied by 5

First typescript (i'll give you a moment)!

















Complete Code

function practice(list: number[], idx: number): number {
  return (list.length > idx ? list[idx] : idx) * 5;
}

function practice2(list: number[], idx: number): number {
  if (idx >= list.length) {
    return idx * 5;
  }
  return list[idx] * 5;
}

const list = [1, 2, 3];

console.log(practice(list, 4));
















Now to the rust version!

I'll give you a moment.

















Complete Version

fn practice(items: Vec<usize>, idx: usize) -> usize {
    return items.get(idx).unwrap_or(&idx) * 5;
}

fn main() {
    let vec = vec![1, 2, 3, 4, 5];

    println!("value: {}", practice(vec, 0));
}
















Would you look at it?

return (list.length > idx ? list[idx] : idx) * 5;
return list.get(idx).unwrap_or(&idx) * 5;

Rust as a language is hard. But there are TONS of utilities that make it really easy to work with. This is just one example. The Option interface


Rusts take on the "Billion" dollar mistake i think is the right move.

















A quick thing about Vectors

  • What's the difference between [] and .get(x)?

    • [] directly accesses the element, if its not there, its like accessing undefined.
    • .get(x) safely handles out of bounds values
  • why unwrap_or(&idx)?

    • .get(x) returns Option<&T>
    • unwrap_or must maintain the same type
















Some(Questions)?

... maybe that joke is a bit nerdy