Skip to content

Optional Values with ard/maybe

The ard/maybe module provides functions for working with optional values. The Maybe type (written as T?) represents a value that may or may not be present.

The maybe module provides:

  • Value creation with maybe::some() and maybe::none()
  • Nullable types for safe representation of optional values
  • Type safety to prevent null pointer errors at compile time
use ard/maybe
use ard/io
fn main() {
let maybe_name: Str? = maybe::some("Alice")
match maybe_name {
name => io::print("Hello, {name}"),
_ => io::print("Hello, stranger")
}
}

Create a Maybe value containing the given value.

use ard/maybe
let value = maybe::some(42)

Create an empty Maybe value. Type parameters must be explicitly provided.

use ard/maybe
let empty: Int? = maybe::none()

All Maybe types have the following methods:

Check if the Maybe contains a value.

use ard/maybe
let val: Int? = maybe::some(42)
if val.is_some() {
// has a value
}

Check if the Maybe is empty.

use ard/maybe
let val: Int? = maybe::none()
if val.is_none() {
// is empty
}

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

use ard/maybe
let val: Int? = maybe::none()
let result = val.or(0) // 0

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

use ard/maybe
let val: Int? = maybe::some(42)
let result = val.expect("expected a value") // 42

Transform a some value with a function that returns a plain value. The result is automatically wrapped in some(...). If the Maybe is none, the callback is not called and none passes through unchanged.

Use map when the transformation always produces a value.

use ard/maybe
let num: Int? = maybe::some(21)
let doubled = num.map(fn(v) { v * 2 })
let value = doubled.or(0) // 42
// none passes through untouched
let empty: Int? = maybe::none()
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 wrapping its return value in some(...) or returning none(). This lets the callback itself decide whether a value is present.

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

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

Use match expressions to safely handle optional values:

use ard/maybe
use ard/io
fn main() {
let maybe_age: Int? = maybe::some(30)
match maybe_age {
age => io::print("Age: {age.to_str()}"),
_ => io::print("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 ard/maybe
use ard/io
fn main() {
let email: Str? = maybe::none()
if email.is_some() {
io::print("Email: {email.or("")}")
} else {
io::print("No email provided")
}
}
use ard/maybe
fn main() {
let theme: Str? = maybe::none()
let selected_theme = theme.or("light")
// selected_theme is "light"
}

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

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