Rust for javascript utviklere - En myk start
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
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
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)
});