Options
Learn about the option type in Move, which represents optional values that may or may not be present.
Options
The Option<T> type defines a generic option that represents a value of type T that may, or may not, be present. It defined in the standard library and provides a type-safe way to handle optional values.
/// Generic type abstraction of a value that may, or may not, be present.
/// Can contain a value of either resource or copyable kind.
struct Option<T>;Option Variants
The Option type has two conceptual variants:
- Some: Contains a value of type
T - None: Represents the absence of a value
These variants provide a type-safe way to handle optional data without relying on null pointers or special sentinel values.
Why Use Option?
Consider an application that takes user input where some fields are required and others are optional. For example, a user's middle name is optional.
While we could use an empty string to represent the absence of a middle name, it would require extra checks to differentiate between an empty string and a missing middle name. Instead, we can use the Option type:
module book::user_registry;
use std::string::String;
use std::option::Option;
/// A struct representing a user record.
public struct User has drop {
first_name: String,
middle_name: Option<String>,
last_name: String,
}
/// Create a new `User` struct with the given fields.
public fun register(
first_name: String,
middle_name: Option<String>,
last_name: String,
): User {
User { first_name, middle_name, last_name }
}In this example, the middle_name field is of type Option<String>. This makes the optional nature of the field clear and type-safe.
Creating Option Values
Creating Empty Options
Use option::none<T>() to create an empty Option that can contain a value of type T:
let empty_name: Option<vector<u8>> = option::none();
let empty_age: Option<u8> = option::none();
let empty_address: Option<address> = option::none();Creating Non-Empty Options
Use option::some<T>(value) to create an Option containing a value:
let name_opt = option::some(b"Alice");
let age_opt = option::some(25u8);
let address_opt = option::some(@0x1);Option Operations
Checking Option State
let opt = option::some(b"Alice");
let empty: Option<u64> = option::none();
// Check if option contains a value
assert!(option::is_some(&opt) == true);
assert!(option::is_some(&empty) == false);
// Check if option is empty
assert!(option::is_none(&opt) == false);
assert!(option::is_none(&empty) == true);Borrowing Values
Return references to the value inside the option:
let opt = option::some(b"Alice");
// Borrow immutable reference (aborts if empty)
let name_ref = option::borrow(&opt);
assert!(*name_ref == b"Alice");
// Borrow with default reference (never aborts)
let default_name = b"Unknown";
let name_or_default = option::borrow_with_default(&opt, &default_name);
// Borrow mutable reference (aborts if empty)
let opt = option::some(100u64);
let value_ref = option::borrow_mut(&mut opt);
*value_refExtracting and Destroying Values
let opt = option::some(b"Alice");
// Extract value, leaving option empty (aborts if empty)
let inner = option::extract(&opt);
assert!(inner == b"Alice");
assert!(option::is_none(&opt));
// Destroy and return value (aborts if empty)
let opt2 = option::some(42u64);
let value = option::destroy_some(opt2);
assert!(value == 42);
// Destroy with default (requires copyable type)
let opt3: Option<u64> = option::none();
let value_or_default = option::destroy_with_default(opt3, 100);
assert!(value_or_default == 100);
// Destroy empty option (aborts if contains value)
let empty_opt: Option<u64> = option::none();
option::destroy_none(empty_opt);Advanced Operations
// Get value with default (requires copyable type)
let opt: Option<u64> = option::some(42);
let value = option::get_with_default(&opt, 100);
assert!(value == 42);
let empty_opt: Option<u64> = option::none();
let default_value = option::get_with_default(&empty_opt, 100);
assert!(default_value == 100);
// Fill empty option with value (aborts if already contains value)
let empty_opt: Option<u64> = option::none();
option::fill(&empty_opt, 42);
assert!(option::is_some(&empty_opt));
// Swap value in option (aborts if empty)
let opt = option::some(100u64);
let old_value = option::swap(&opt, 200);
assert!(old_value == 100);
assert!(*option::borrow(&opt) == 200);
// Check if option contains specific value
let opt = option::some(42u64);
assert!(option::contains(&opt, &42) == true);
assert!(option::contains(&opt, &100) == false);Common Usage Patterns
Safe Value Access with Default
public fun get_user_display_name(user: &User): vector<u8> {
if (option::is_some(&user.middle_name)) {
let middle = option::borrow(&user.middle_name);
// Construct full name with middle name
b"Full name with middle"
} else {
// Construct name without middle name
b"Name without middle"
}
}Option in Function Parameters
public fun create_user_with_optional_email(
name: vector<u8>,
email: Option<vector<u8>>
): User {
User { name, email, verified: false }
}
// Usage
let user1 = create_user_with_optional_email(
b"Alice",
option::some(b"alice@example.com")
);
let user2 = create_user_with_optional_email(
b"Bob",
option::none()
);Option in Return Values
public fun find_user_by_id(users: &vector<User>, id: u64): Option<User> {
let i = 0;
let len = vector::length(users);
while (i < len) {
let user = vector::borrow(users, i);
if (user.id == id) {
return option::some(*user)
};
i = i + 1;
};
option::none()
}Practical Examples
Configuration with Defaults
module app::config {
public struct Config has key {
max_users: Option<u64>,
timeout: Option<u64>,
}
public fun get_max_users_or_default(config: &Config): u64 {
option::get_with_default(&config.max_users, 1000)
}
public fun get_timeout_or_default(config: &Config): u64 {
option::get_with_default(&config.timeout, 30)
}
}Safe Mathematical Operations
module app::math {
public fun safe_divide(a: u64, b: u64): Option<u64> {
if (b == 0) {
option::none()
} else {
option::some(a / b)
}
}
public fun safe_sqrt(x: u64): Option<u64> {
// Simplified square root that only works for perfect squares
let i = 0;
while (i * i <= x) {
if (i * i == x) {
return option::some(i)
};
i = i + 1;
};
option::none()
}
}Best Practices
1. Use Option for Truly Optional Data
// Good: Optional fields that may genuinely be absent
public struct User {
name: vector<u8>,
email: Option<vector<u8>>, // Optional
phone: Option<vector<u8>>, // Optional
}
// Avoid: Using Option for required data
public struct User {
name: Option<vector<u8>>, // Don't do this if name is required
}2. Prefer get_with_default for Copyable Types
// Good: Use get_with_default for simple defaults
public fun get_retry_count(config: &Config): u8 {
option::get_with_default(&config.retry_count, 3)
}
// Less ideal: Manual checking
public fun get_retry_count_manual(config: &Config): u8 {
if (option::is_some(&config.retry_count)) {
*option::borrow(&config.retry_count)
} else {
3
}
}3. Handle Resource Types Carefully
// For resource types, you cannot use get_with_default
public fun process_resource_option(opt: Option<SomeResource>): SomeResource {
if (option::is_some(&opt)) {
option::extract(&opt)
} else {
// Must create new resource, cannot use default
create_new_resource()
}
}Ownership
Option values follow standard Move ownership rules:
- Copy: If
Thascopy, thenOption<T>hascopy - Drop: If
Thasdrop, thenOption<T>hasdrop - Store: Option always has
storeability
let opt1 = option::some(10u64); // u64 has copy
let opt2 = copy opt1; // Can copy because u64 has copy
let opt3 = option::some(vector[1, 2, 3]); // vector doesn't have copy
// let opt4 = copy opt3; // Error: cannot copy
let opt4 = opt3; // Must moveImportant: Some operations like get_with_default and destroy_with_default require the element type T to have the copy ability.
Summary
The Option type in Move provides:
- Type-safe handling of values that may or may not be present
- Resource-aware operations that work with both copyable and resource types
- Comprehensive API for creating, checking, borrowing, and destroying options
- Explicit null handling without null pointer errors
Key operations:
- Creation:
none(),some() - Checking:
is_some(),is_none(),contains() - Access:
borrow(),borrow_mut(),borrow_with_default() - Extraction:
extract(),get_with_default(),swap() - Destruction:
destroy_some(),destroy_none(),destroy_with_default()
Use Option for:
- Optional struct fields
- Function parameters that may be omitted
- Return values from operations that may fail
- Configuration settings with defaults
- Safe handling of potentially absent data
The Option type is essential for writing robust Move code that handles the absence of values safely and explicitly.