Rust для JavaScript разработчиков
Ownership, borrowing, zero-cost abstractions и безопасность памяти
Rust — самый любимый язык по Stack Overflow 8 лет подряд. Memory safety без garbage collector, performance как у C++, но с современным синтаксисом. Mozilla, Discord, AWS используют Rust. Разберем основы Rust для JS разработчиков. Почему Rust Memory safety — нет segfaults, use-after-free Concurrency без data races Zero-cost abstractions No garbage collector — предсказуемая производительность Rich type system Основы Variables: // JS let x = 5; x = 6; // OK const y = 5; y = 6; // Error // Rust let x = 5; x = 6; // Error - immutable by default let mut x = 5; x = 6; // OK const Y: i32 = 5; // Compile-time constant Types: // JS - dynamic let x = 5; x = "hello"; // OK // Rust - static let x: i32 = 5; // x = "hello"; // Error - type mismatch // Type inference let x = 5; // i32 inferred let y = 5.0; // f64 inferred Ownership Ключевая концепция Rust: // JS - все по reference let s1 = { value: "hello" }; let s2 = s1; console.log(s1.value); // OK // Rust - ownership transfer let s1 = String::from("hello"); let s2 = s1; // println!("{}", s1); // Error - s1 no longer valid println!("{}", s2); // OK Правила ownership: Каждое значение имеет owner Может быть только один owner Когда owner выходит из scope, значение drop Borrowing // Immutable borrow let s1 = String::from("hello"); let len = calculate_length(&s1); // Borrow println!("{} has length {}", s1, len); // s1 still valid fn calculate_length(s: &String) -> usize { s.len() } // Mutable borrow let mut s = String::from("hello"); change(&mut s); fn change(s: &mut String) { s.push_str(", world"); } Borrowing rules: Либо один mutable reference Либо любое количество immutable references References всегда валидны (no dangling) Functions // JS function add(a, b) { return a + b; } // Rust fn add(a: i32, b: i32) -> i32 { a + b // No semicolon = return } // Or explicit return fn add(a: i32, b: i32) -> i32 { return a + b; } Option Type (вместо null) // JS function findUser(id) { return users.find(u => u.id === id) || null; } const user = findUser(123); if (user) { // null check console.log(user.name); } // Rust fn find_user(id: u32) -> Option { users.iter().find(|u| u.id == id).cloned() } let user = find_user(123); match user { Some(u) => println!("{}", u.name), None => println!("User not found") } // Or with if let if let Some(u) = user { println!("{}", u.name); } Result Type (error handling) // JS function divide(a, b) { if (b === 0) { throw new Error("Division by zero"); } return a / b; } try { const result = divide(10, 0); } catch (e) { console.error(e.message); } // Rust fn divide(a: f64, b: f64) -> Result { if b == 0.0 { Err(String::from("Division by zero")) } else { Ok(a / b) } } match divide(10.0, 0.0) { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e) } // Or with ? operator fn calculate() -> Result { let result = divide(10.0, 2.0)?; // Propagate error Ok(result * 2.0) } Structs // JS class User { constructor(name, email) { this.name = name; this.email = email; } greet() { console.log(`Hello, ${this.name}`); } } // Rust struct User { name: String, email: String, } impl User { fn new(name: String, email: String) -> Self { User { name, email } } fn greet(&self) { println!("Hello, {}", self.name); } } let user = User::new( String::from("John"), String::from("john@example.com") ); Enums // JS - often just strings const status = "pending"; // or "approved" or "rejected" // Rust - proper enums enum Status { Pending, Approved, Rejected, } let status = Status::Pending; match status { Status::Pending => println!("Waiting..."), Status::Approved => println!("Approved!"), Status::Rejected => println!("Rejected") } // Enums with data enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } let msg = Message::Write(String::from("hello")); Vectors (arrays) // JS const arr = [1, 2, 3]; arr.push(4); console.log(arr[0]); // Rust let mut vec = vec![1, 2, 3]; vec.push(4); println!("{}", vec[0]); // Or with get (returns Option) if let Some(value) = vec.get(0) { println!("{}", value); } // Iteration for value in &vec { println!("{}", value); } Hash Maps // JS const map = new Map(); map.set("key", "value"); console.log(map.get("key")); // Rust use std::collections::HashMap; let mut map = HashMap::new(); map.insert("key", "value"); match map.get("key") { Some(value) => println!("{}", value), None => println!("Not found") } Iterators // JS const numbers = [1, 2, 3, 4, 5]; const doubled = numbers .filter(n => n % 2 === 0) .map(n => n * 2); // Rust let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec = numbers .iter() .filter(|&n| n % 2 == 0) .map(|&n| n * 2) .collect(); Async/Await // JS async function fetchUser(id) { const response = await fetch(`/api/users/${id}`); return response.json(); } // Rust (with tokio) use tokio; async fn fetch_user(id: u32) -> Result { let response = reqwest::get(&format!("https://api.example.com/users/{}", id)) .await?; let user = response.json:: ().await?; Ok(user) } #[tokio::main] async fn main() { match fetch_user(123).await { Ok(user) => println!("{:?}", user), Err(e) => eprintln!("Error: {}", e) } } Traits (interfaces) // JS class Animal { makeSound() { throw new Error("Must implement"); } } class Dog extends Animal { makeSound() { console.log("Woof!"); } } // Rust trait Animal { fn make_sound(&self); } struct Dog; impl Animal for Dog { fn make_sound(&self) { println!("Woof!"); } } fn pet_animal(animal: &impl Animal) { animal.make_sound(); } Pattern Matching // JS - switch switch (value) { case 1: console.log("one"); break; case 2: console.log("two"); break; default: console.log("other"); } // Rust - match (exhaustive) match value { 1 => println!("one"), 2 => println!("two"), _ => println!("other") } // Destructuring match point { Point { x: 0, y: 0 } => println!("Origin"), Point { x, y: 0 } => println!("On x axis at {}", x), Point { x: 0, y } => println!("On y axis at {}", y), Point { x, y } => println!("({}, {})", x, y) } Closures // JS const add = (a, b) => a + b; const addFive = (x) => add(x, 5); // Rust let add = |a, b| a + b; let add_five = |x| add(x, 5); // Capturing environment let factor = 2; let multiply = |x| x * factor; Lifetimes // JS - не нужны, GC управляет function longest(s1, s2) { return s1.length > s2.length ? s1 : s2; } // Rust - нужно указать relationship fn longest (s1: &'a str, s2: &'a str) -> &'a str { if s1.len() > s2.len() { s1 } else { s2 } } // Compiler гарантирует что returned reference валиден Testing // JS test('adds numbers', () => { expect(add(2, 3)).toBe(5); }); // Rust #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(add(2, 3), 5); } #[test] fn test_divide() { assert!(divide(10.0, 2.0).is_ok()); assert!(divide(10.0, 0.0).is_err()); } } WebAssembly // Rust to WASM use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn fibonacci(n: u32) -> u32 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2) } } // JS calling Rust WASM import init, { fibonacci } from './pkg/my_crate.js'; await init(); const result = fibonacci(10); console.log(result); Cargo (package manager) # Create new project cargo new my_project cd my_project # Add dependency cargo add tokio --features full # Build cargo build # Run cargo run # Test cargo test # Cargo.toml (like package.json) [package] name = "my_project" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } reqwest = "0.11" Common Crates (libraries) tokio — async runtime serde — serialization reqwest — HTTP client actix-web — web framework sqlx — SQL toolkit clap — CLI parsing Learning Path The Rust Book — официальная книга Rustlings — интерактивные упражнения Rust by Example — примеры кода Practice — маленькие проекты Заключение: Rust сложнее JavaScript, но дает беспрецедентный контроль. Ownership гарантирует memory safety. Type system ловит ошибки на этапе компиляции. Performance как у C++. Начните с The Rust Book. Практикуйте ownership и borrowing. Rust — инвестиция в будущее. WebAssembly открывает Rust для веба. Попробуйте!