Option, Result, and No Null
TypeScript has null, undefined, and exceptions — Rust has none of them
Section titled “TypeScript has null, undefined, and exceptions — Rust has none of them”TypeScript (and JavaScript) have three ways to represent “something went wrong”:
null— deliberate absence of a valueundefined— an uninitialized or missing value- Thrown exceptions — errors that escape the call stack invisibly
All three are invisible in function signatures. A function that returns string might silently return null, throw an error, or work fine — you cannot tell from the type.
Rust has no null, no undefined, and no exceptions. Absent values are Option<T>. Fallible operations return Result<T, E>. Both are just enum variants, visible in every function signature.
Option — the no-null type
Section titled “Option — the no-null type”Option<T> is an enum with two variants: Some(T) (there is a value) and None (there is no value). The compiler forces you to handle both.
// TypeScript — null can appear silentlyfunction findUser(id: number): string | null { if (id === 1) return "Alice"; return null; // easy to forget to check}
const user = findUser(2);console.log(user?.toUpperCase() ?? "not found");// Without optional chaining: user.toUpperCase() would throw TypeErrorfn find_user(id: u32) -> Option<String> { if id == 1 { Some("Alice".to_string()) } else { None }}
fn main() { // Pattern match — must handle both cases match find_user(2) { Some(name) => println!("found: {}", name), None => println!("not found"), }
// Ergonomic helpers let upper = find_user(1) .map(|s| s.to_uppercase()) .unwrap_or_else(|| "NOT FOUND".to_string()); println!("{}", upper);}Result — the no-exceptions type
Section titled “Result — the no-exceptions type”Result<T, E> is an enum with two variants: Ok(T) (success) and Err(E) (failure). Fallible functions return Result, making errors visible in the type signature.
// TypeScript — exceptions are invisible in signaturesfunction parseNumber(s: string): number { const n = parseInt(s); if (isNaN(n)) throw new Error(`not a number: ${s}`); return n;}
try { const n = parseNumber("abc"); console.log(n * 2);} catch (e) { // Easy to forget try/catch — and the signature gave no hint console.error(e);}fn parse_and_double(s: &str) -> Result<i32, String> { let n: i32 = s.parse().map_err(|_| format!("not a number: {}", s))?; Ok(n * 2)}
fn main() { match parse_and_double("21") { Ok(v) => println!("doubled: {}", v), Err(e) => println!("error: {}", e), }
match parse_and_double("abc") { Ok(v) => println!("doubled: {}", v), Err(e) => println!("error: {}", e), }}Try it
Section titled “Try it”fn parse_and_double(s: &str) -> Result<i32, String> { let n: i32 = s.parse().map_err(|_| format!("not a number: {}", s))?; Ok(n * 2)}
fn main() { match parse_and_double("21") { Ok(v) => println!("doubled: {}", v), Err(e) => println!("error: {}", e), }
let maybe: Option<i32> = Some(42); if let Some(v) = maybe { println!("got: {}", v); }
let none: Option<i32> = None; println!("unwrap_or: {}", none.unwrap_or(0));}Compiling…