Rust for javascript utviklere - En myk start

Christian Sandven

Selvom Rust og JavaScript er veldig forskjellige både i syntax og utviklingsmetode, er det noen ting man kan sammeligne. Dette er en liten oversikt over likheter og ulikheter i disse språkene.

Dette innlegget er ment som en start-guide for Javascript utviklere som ønsker å lære Rust. Den tar for seg enkle temaer som gir et godt grunnlag.

Eksemplene er alltid JavaScript øverst og Rust nederst. I de fleste tilfeller, er main funksjonen i Rust utelatt.

Litt om likheter

Deklarasjon av variabler

let a = 1; // En variabel som kan forandre seg
const b = 2; // En variabel som ikke kan forandre seg
let a = 1 // En variabel som ikke kan forandre seg
let mut b = 2 // En variabel som kan forandre seg

Løkker

for (let i = 0; i < 5; i++) {
  console.log(i);
}

const objectList = [{ a: 1 }, { a: 2 }, { a: 3 }];
objectList.forEach((item) => console.log(item));
for i in 0..5 {
    println!("{}", i);
}

#[derive(Debug)]
struct O {
    pub a: i32
}
let object_list = vec![
    O{a: 1}, O{a: 2}, O{a: 3}
];
object_list.iter().for_each(|item| {
    dbg!(item);
});

If statement

const a = 1;
const b = 2;
let isTrue = false;
if (a === b) {
  isTrue = true;
}
let a = 1;
let b = 2;
let is_true = if a == b {
    true
} else {
    false
};

Deklarasjon av funksjoner

const f = () => 1;
const n = (a) => 1 + a;
fn f() -> i32 { 1 }
fn n(a: i32) -> i32 { 1 + a }

Data objekter

const o = {
  a: 1,
  b: 2,
};
struct O {
    pub a: i32,
    pub b: i32
}
let o = O { a: 1, b: 2 };

Klasser / Structs

I rust er det ikke noe begrep om klasser, men det går likevel an å implementere funksjoner i en struct.

class Potato {
  constructor(juicyness) {
    this.juicyness = juicyness;
  }

  isJuicy() {
    return this.juicyness > 10;
  }
}

const potato = new Potato(11);
if (potato.isJuicy()) {
  console.log('The potato is juicy');
}
struct Potato {
    juicyness: u8
}

impl Potato {
    pub fn new(juicyness: u8) -> Self {
        Potato {
            juicyness
        }
    }

    pub fn is_juicy(&self) -> bool {
        self.juicyness > 10
    }
}

let potato = Potato::new(11);
if potato.is_juicy() {
    println!("The potato is juicy")
}

Match: en switch på steroider

const a = 1;
switch (a) {
  case 1:
    console.log('1');
    break;
  case 2:
    console.log('2');
    break;
  case 3:
  case 4:
    console.error('Can not be 3 or 4');
  default:
    console.error('No case for', a);
}

let b = 0;
let c = 2;
switch (c) {
  case 1:
    b = 1;
    break;
  case 2:
    b = 2;
    break;
  case 3:
  case 4:
    b = null;
}

function returnFromSwitch(a) {
  switch (a) {
    case 1:
      return 1;
    case 2:
      return 2;
    case 3:
    case 4:
      return null;
    default:
      return 2;
  }
}
let a = 1;
match a {
    1 => { println!("1"); },
    2 => { println!("2"); },
    3 | 4 => { eprintln!("Can not be 3 or 4"); },
    _ => { eprintln!("No case for {}", a); }
}

let c = 2;
let b = match c {
    1 => { Some(1) },
    2 => { Some(1) },
    3 | 4 => { Option::None },
    _ => { Some(c) }
};

fn return_from_match(a: i32) -> Option<i32> {
    match a {
        1 => { Some(1) },
        2 => { Some(1) },
        3 | 4 => { Option::None },
        _ => { Some(c) }
    }
}

Litt om forskjeller

Null values

I Rust finnes ikke null verdier, og det nærmeste man kommer er Option typer.

let a = null;
a = 1;
let mut a: Option<i32> = Option::None;
a = Some(1);

Hint: I virkeligheten trenger man bare å skrive let mut a = Option::None siden compileren finner ut av typen på linje 2.

Videre eksempel

I javascript kan man for eksempel sammeligne a med b uansett om den er et tall eller null.

let a = null;
let b = 1;
let c = a === b; // false
let mut a: Option<i32> = Option::None;
let b = 1;
let c = a == b; // Throws: expected enum `Option`, found integer
let c = a.unwrap() == b // Throws: called `Option::unwrap()` on a `None` value
let c = a == Some(b) // false

Async await

Både Rust og Javascript har async/await, disse brukes også til ganske tilsvarende ting. Som å spørre en database, sende requester til servere etc.

I javascript returnerer en async funksjon en Promise type, som kan defineres som Promise. I Rust er det en lignende type som er Result<T, E>

const fs = require('fs').promises;
const loadFile = async () => {
  const data = await fs.readFile('myFile.txt', 'binary');
  return Buffer.from(data);
};

const useLoadFile = async () => {
  try {
    let data = await loadFile();
    return data;
  } catch (e) {
    console.error('loadFile failed with error:', e);
    return null;
  }
};
use thiserror::Error;

#[derive(Error, Debug)]
enum MyError {
    #[error("IO error")]
    Io(#[from] std::io::Error)
}

// Denne trenger ikke egentlig å være async, men jeg ville holde eksempelet enkelt
async fn load_file () -> std::result::Result<String, MyError> {
    Ok(std::fs::read_to_string("myFile.txt")?)
}

#[tokio::main]
async fn main() {
    let result = load_file().await;
    match result {
        Ok(_) => println!("Yay"),
        Err(_) => println!("Woops")
    }
}

Funksjon som argument

I javascript er/var dette et mye brukt mønster. I Rust er det ikke like vanlig å bruke det etter min erfaring.

const myFunction = (callback) => {
  callback([1, 2]);
};
myFunction(([a, b]) => console.log(a, b));
fn my_function<F>(callback: F) where F: Fn((i32, i32)) -> () {
    callback((1, 2))
}
my_function(|(a, b)| {
    println!("{}, {}", a, b)
});