Skip to content

Option and Result

TypeScript uses T | null | undefined for optional values and try/catch for errors. Rust encodes both in the type system:

  • Option<T> — either Some(value) or None. No null, no undefined.
  • Result<T, E> — either Ok(value) or Err(error). No exceptions.

The type system forces you to handle both cases before you can use the value.

// TypeScript: null can silently propagate
function findUser(id: number): User | null { ... }
const user = findUser(42);
console.log(user.name); // runtime crash if null
// Rust: the compiler won't let you use the value without handling None
fn find_user(id: u32) -> Option<User> { ... }
match find_user(42) {
Some(user) => println!("{}", user.name),
None => println!("not found"),
}
// TypeScript: error can be thrown anywhere
function divide(a: number, b: number): number {
if (b === 0) throw new Error("division by zero");
return a / b;
}
// Rust: error is part of the return type
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 { Err("division by zero".to_string()) }
else { Ok(a / b) }
}
TypeScriptRust
value ?? defaultValoption.unwrap_or(defaultVal)
value! (non-null assertion)option.unwrap() (panics if None)
value ?? (() => { throw ... })()option.expect("msg") (panics with message)

In a function that returns Result, ? on a Result value either unwraps Ok or returns the Err early — eliminating nested match chains:

fn read_number(s: &str) -> Result<i32, std::num::ParseIntError> {
let n = s.trim().parse::<i32>()?; // returns Err early if parse fails
Ok(n * 2)
}
TypeScript
// Option-like: null / undefined
function divide(a: number, b: number): number | null {
if (b === 0) return null;
return a / b;
}
const r = divide(10, 2) ?? 0;
console.log(r); // 5
// Error handling: try/catch
try {
const val = JSON.parse("bad json");
} catch (e) {
console.error("parse error", e);
}
Rust
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("division by zero"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(v) => println!("{v}"), // 5
Err(e) => println!("{e}"),
}
let maybe: Option<i32> = Some(42);
let value = maybe.unwrap_or(0);
println!("{value}"); // 42
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("division by zero"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("10 / 2 = {result}"),
Err(e) => println!("Error: {e}"),
}
match divide(5.0, 0.0) {
Ok(result) => println!("5 / 0 = {result}"),
Err(e) => println!("Error: {e}"),
}
let maybe: Option<i32> = Some(42);
let value = maybe.unwrap_or(0);
println!("Option value: {value}");
}
What does `Option<T>` represent in Rust?
What does the `?` operator do on a `Result` in Rust?
Which method returns a default value if an Option is None?