Option and Result
No null: use Option<T>
Section titled “No null: use Option<T>”Vertex has no null. Absence of a value is expressed as Option<T>, which is a sum type with two variants:
Some(value). A value is presentNone. No value
fn find_user(id: Int): Option<String> { if id == 1 { Some("Alice") } else { None }}
match find_user(42) { Some(name) => debug "Found: ${name}", None => debug "Not found",}Working with Option
Section titled “Working with Option”let name = find_user(1)
// Unwrap with a defaultlet n: String = name.unwrapOr("anonymous")
// Transform if presentlet upper = name.map(fn(s: String): String { s.toUpperCase() })
// Pipelinelet label = name .map(fn(s: String): String { "Hello, " + s }) .unwrapOr("Hello, stranger")
// Convert to Resultlet result = name.toResult("user not found")No exceptions: use Result<T, E>
Section titled “No exceptions: use Result<T, E>”Vertex has no throw/try/catch. Fallible operations return Result<T, E>:
Ok(value). SuccessError(e). Failure
fn safe_divide(a: Int, b: Int): Result<Int, String> { if b == 0 { Error("cannot divide by zero") } else { Ok(a / b) }}
match safe_divide(10, 2) { Ok(v) => debug "result: ${v}", Error(e) => debug "error: ${e}",}The ? operator
Section titled “The ? operator”? is the error propagation operator. Apply it to a Result<T, E> expression:
Ok(value)→ unwraps tovalue, execution continuesError(e)→ immediately returnsError(e)from the enclosing function
fn validate_age(age: Int): Result<Int, String> { if age < 0 { Error("age cannot be negative") } else { Ok(age) }}
fn process(raw: Int): Result<String, String> { let age = validate_age(raw)? // propagates Error if validation fails Ok(if age < 18 { "minor" } else { "adult" })}
debug match process(25) { Ok(s) => s, Error(e) => "invalid: " + e } // adultdebug match process(-1) { Ok(s) => s, Error(e) => "invalid: " + e } // invalid: age cannot be negativeCombinators
Section titled “Combinators”let r: Result<Int, String> = safe_divide(10, 2)
let doubled = r.map(fn(x: Int): Int { x * 2 }) // Result<Int, String>let remapped = r.mapError(fn(e: String): Int { e.length() }) // Result<Int, Int>let final = r.flatMap(fn(x: Int): Result<Int, String> { Ok(x + 1) }).unwrapOr(0)let opt = r.toOption() // Option<Int>Optional fields in constructors
Section titled “Optional fields in constructors”Any field typed Option<T> in a constructor can be omitted. It defaults to None:
type Contact { Contact(name: String, phone: Option<String>)}
let c1 = Contact(name: "Alice") // phone: Nonelet c2 = Contact(name: "Bob", phone: Some("+1 555")) // phone: SomeNext steps
Section titled “Next steps”- Option<T> reference
- Result<T, E> reference
- Pattern Matching: match on both types