Skip to content

Optional Values with Maybe

Maybe is a built-in optional type. It represents a value that may or may not be present and can be written either as the formal type Maybe<T> or the shorthand T?.

Maybe provides:

  • Value creation with Maybe::new(value) and Maybe::new<T>()
  • Nullable types for safe representation of optional values
  • Mutation helpers (set/clear) for mutable optional slots
  • Type safety to prevent null pointer errors at compile time
use go:fmt
fn main() {
let maybe_name: Str? = Maybe::new("Alice")
match maybe_name {
name => fmt::Println("Hello, {name}"),
_ => fmt::Println("Hello, stranger")
}
}

Create a Maybe value. Pass a value to create a present value, or omit the nullable argument to create an empty value.

let value: Maybe<Int> = Maybe::new(42)
let shorthand: Int? = Maybe::new(42)

When the argument is omitted, the type parameter may be inferred from context, or provided explicitly when there is no context.

let empty: Int? = Maybe::new()
let explicit = Maybe::new<Int>()

All Maybe types have the following methods:

Check if the Maybe contains a value.

let val: Int? = Maybe::new(42)
if val.is_some() {
// has a value
}

Check if the Maybe is empty.

let val: Int? = Maybe::new()
if val.is_none() {
// is empty
}

Get the value from the Maybe, or return a default if it’s empty.

let val: Int? = Maybe::new()
let result = val.or(0) // 0

Get the value from the Maybe, or panic with a message if it’s empty.

let val: Int? = Maybe::new(42)
let result = val.expect("expected a value") // 42

Transform a present value with a function that returns a plain value. The result is automatically wrapped as present. If the Maybe is empty, the callback is not called and the empty value passes through unchanged.

Use map when the transformation always produces a value.

let num: Int? = Maybe::new(21)
let doubled = num.map(fn(v) { v * 2 })
let value = doubled.or(0) // 42
// empty values pass through untouched
let empty: Int? = Maybe::new()
empty.map(fn(v) { v * 2 }).is_none() // true

You can also provide explicit type arguments when you want to guide inference:

let as_text = num.map<Str>(fn(v) { "{v}" })

Chain operations that return a Maybe themselves (also known as flat_map in other languages). Unlike map, the callback is responsible for returning Maybe::new(value) or Maybe::new<T>(). This lets the callback itself decide whether a value is present.

Use and_then when the next step might not produce a value.

fn even_only(num: Int) Int? {
match num % 2 == 0 {
true => Maybe::new(num),
false => Maybe::new(),
}
}
let result = Maybe::new(20).and_then(even_only)
result.is_some() // true
// The callback can return none, unlike map:
let odd = Maybe::new(21).and_then(even_only)
odd.is_none() // true

Mutate a Maybe<T> slot to contain value. The receiver must be mutable.

mut current = Maybe::new<Int>()
current.set(42)
current.expect("set") // 42

Mutate a Maybe<T> slot back to none. The receiver must be mutable.

mut current = Maybe::new("ready")
current.clear()
current.is_none() // true

Use match expressions to safely handle optional values:

use go:fmt
fn main() {
let maybe_age: Int? = Maybe::new(30)
match maybe_age {
age => fmt::Println("Age: {age.to_str()}"),
_ => fmt::Println("Age unknown")
}
}

When a Maybe value is matched:

  • The first pattern captures the inner value if present
  • The _ pattern matches when the value is absent (none)
use go:fmt
fn main() {
let email: Str? = Maybe::new()
if email.is_some() {
let address = email.or("unknown")
fmt::Println("Email: {address}")
} else {
fmt::Println("No email provided")
}
}
fn main() {
let theme: Str? = Maybe::new()
let selected_theme = theme.or("light")
// selected_theme is "light"
}

Nullable struct fields accept unwrapped values directly — they are automatically wrapped in Maybe::new():

struct User {
name: Str,
bio: Str?,
}
fn main() {
// bio is automatically wrapped in Maybe::new()
let user = User{
name: "Alice",
bio: "Software engineer",
}
match user.bio {
description => {
// has bio
},
_ => {
// no bio
}
}
}
use go:fmt
fn get_user_name(user_id: Int) Str? {
if user_id == 1 {
Maybe::new("Alice")
} else {
Maybe::new()
}
}
fn main() {
let name = get_user_name(1)
fmt::Println(name.or("Unknown user"))
}
use ard/list
fn main() {
let values: [Int?] = [
Maybe::new(1),
Maybe::new(),
Maybe::new(3)
]
// Using list operations with optional values
}