Sui.

Post

Share your knowledge.

SuiLover.
Jul 30, 2025
Expert Q&A

How Do I represent optional Values in Move Using Option <T>?

I'm trying to understand this aspect of the Sui Network because I'm either building, debugging, or deploying something that touches this area. I want a detailed explanation of how this mechanism or feature works, along with relevant CLI usage, Move code structure, or architectural concepts. My goal is to gain enough clarity to apply this knowledge in a real project—whether that's a custom smart contract, an NFT system, a wallet integration, or a DeFi tool. The Sui Network has unique features compared to EVM chains, so I'm particularly interested in what sets it apart and how that affects development best practices. It would help to have sample code, command line examples, or typical errors to watch for, especially when using the Sui CLI, SDK, or deploying on localnet/testnet. Ultimately, I want to avoid common mistakes, follow the best security principles, and ensure that the functionality I’m working on behaves as expected under realistic conditions.

  • Sui
  • SDKs and Developer Tools
  • Move
3
14
Share
Comments
.

Answers

14
Paul.
Paul4340
Jul 31 2025, 05:42

In Move, optional values are represented using Option<T>, which is an enum type that can either contain a value of type T or be empty (None).

Key Concepts:

  • Option<T> has two variants:

    1. Some(T) – Contains a value of type T.
    2. None – Represents the absence of a value.

Example Code:

module MyModule {
    // Define an optional integer
    public fun get_optional_value(): Option<u64> {
        // Return Some with a value
        return Option::Some(42);
        
        // Alternatively, return None
        // return Option::None<u64>();
    }

    public fun use_optional_value(opt: Option<u64>) {
        match opt {
            Option::Some(value) => {
                // Do something with the value
                std::debug::print(&value);
            },
            Option::None => {
                // Handle absence of value
                std::debug::print(&"No value");
            }
        }
    }
}

CLI Usage:

You can pass an Option<T> as an argument in function calls, depending on the contract.

sui client call --function get_optional_value --package <package-id> --module <module-name>

Best Practices:

  • Use Option<T> for values that might not be present (e.g., optional fields, default values).
  • Properly handle both Some and None cases in your code to avoid runtime errors.
7
Comments
.
Ashford.
Jul 31 2025, 06:40

In Move, you can represent optional values using the Option<T> type, which allows a value to either be present (Some<T>) or absent (None). This is similar to how option types are used in languages like Rust or Swift.

Key Concepts:

  • Option<T> is an enum type in Move that can either be:

    • Some(T): Contains a value of type T.
    • None: Represents the absence of a value.

This type is useful when you need to handle cases where a value might not always be available, such as optional fields in a smart contract, or when a function might not always return a result.

Move Representation:

Here’s how Option<T> is defined and used in Move:

module MyModule {
    // Define an Option type for a coin (for example)
    struct Option<T> has store {
        value: T,
        is_some: bool,
    }

    // Function to create a Some value
    public fun some<T>(value: T): Option<T> {
        Option { value, is_some: true }
    }

    // Function to create a None value
    public fun none<T>(): Option<T> {
        Option { value: T, is_some: false }
    }

    // Function to check if an option has a value
    public fun is_some<T>(opt: &Option<T>): bool {
        opt.is_some
    }

    // Function to unwrap the value of an Option if it is Some
    public fun unwrap<T>(opt: &mut Option<T>): T {
        if (opt.is_some) {
            move(opt).value
        } else {
            abort 1;  // Abort if the Option is None
        }
    }
}

Explanation of Functions:

  • some<T>(value: T): Wraps a value in Some(value).
  • none<T>(): Creates a None value, representing the absence of a value.
  • is_some<T>(opt: Option<T>): Checks if the Option is Some or None.
  • unwrap<T>(opt: Option<T>): Returns the value if it’s Some, or aborts the transaction if it’s None.

Example Usage:

Here’s an example of how to use Option<T> in a smart contract, such as a coin transfer contract that might have an optional fee:

module CoinTransfer {

    use 0x1::Option;

    struct Coin {
        balance: u64,
    }

    // Transfer coins, using an optional fee
    public fun transfer_with_fee(
        sender: &mut Coin, 
        receiver: &mut Coin, 
        amount: u64,
        fee: Option<u64>
    ) {
        // Deduct fee if it's Some, otherwise do nothing
        if (Option::is_some(&fee)) {
            let fee_value = Option::unwrap(&mut fee);
            sender.balance = sender.balance - fee_value;
        }

        // Transfer the amount
        sender.balance = sender.balance - amount;
        receiver.balance = receiver.balance + amount;
    }
}

Explanation:

  • Option<u64> for fee: The fee is an optional value that may or may not be provided. If it’s Some(fee), the transaction deducts the fee from the sender’s balance.
  • is_some and unwrap: We check if the fee is present, and if it is, we apply it to the sender's balance.

CLI Example:

You interact with the contract through the Sui CLI to deploy and test the Move code.

# Publish the module
sui client publish --gas-budget 10000 --module path_to_module

# Call a function with an option parameter
sui client call --gas-budget 10000 --function transfer_with_fee --args <sender-address> <receiver-address> <amount> <option-fee> --network testnet

Where <option-fee> would be either Some(fee) or None depending on whether you want to provide a fee.

Common Errors:

  1. Incorrect Unwrapping: Trying to unwrap a None value will cause a transaction to abort. Always check with is_some before unwrapping.
  2. Mismatched Types: Ensure that the type of the value inside Option matches what’s expected in the function (e.g., Option<u64> for optional fee amounts).
  3. State Management: If you don’t correctly manage the state of Option<T>, it can lead to runtime errors when interacting with resources or performing operations on empty values.

Best Practices:

  • Always Check is_some: Before accessing the value inside an Option, always check with is_some to avoid runtime errors.
  • Handle None Gracefully: When possible, define alternative behavior when an Option is None (like using default values).
  • Test on Testnet: Always test your Option<T> handling logic on the Testnet to ensure there are no unintended side effects.

Summary:

Option<T> in Move allows you to represent optional values, similar to other languages. It's a powerful tool for smart contracts where certain fields or results may not always be present, like optional fees or parameters. Be sure to handle None values properly to avoid errors during transaction execution.

7
Comments
.
Benjamin XDV.
Jul 31 2025, 09:48

In Move, optional values are handled using the Option<T> type from the standard library (0x1::option), which enforces explicit null-checking through its Some(<T>) and None variants. Unlike Solidity's implicit nulls, Move requires pattern matching via option::is_some() and option::extract() to safely access wrapped values, preventing null reference errors at compile time. For Sui objects, this is particularly useful when dealing with dynamic fields or optional resource ownership, as it maintains type safety across network interactions. Common pitfalls include forgetting to check is_some() before extraction or mishandling nested Option types—always use the option module's functions rather than manual unwrapping.

7
Comments
.
BigSneh.
Jul 30 2025, 14:12

In Move, the Option type is a powerful tool to represent values that may or may not be present—similar to Option in Rust. This is especially useful in the Sui Network when writing smart contracts that manage data which can be conditionally initialized, accessed, or mutated. Here's a detailed breakdown of how to use it effectively:

  1. What is Option in Move?

Option is an enum defined in the 0x1::option or 0x2::option module (Sui-specific versions are in 0x2::option). It has two variants:

some: T – Represents the presence of a value.

none – Represents the absence of a value.

use sui::option::{Self, Option};

let value: Option = Option::some(100); let empty: Option = Option::none();

  1. Declaring Optional Fields in Structs

struct UserProfile has key { id: u64, nickname: Option, }

This allows the nickname to be either present (Some(String)) or not (None).

  1. Pattern Matching

To work with the value inside Option, you use the borrow, extract, or is_some/is_none functions:

if (option::is_some(&user.nickname)) { let name = option::borrow(&user.nickname); // Use the name }

Or to extract:

if (option::is_some(&user.nickname)) { let name = option::extract(&mut user.nickname); // Do something with name }

Note: Once extracted, the Option becomes None.

  1. Best Practices

Avoid panics: Don’t extract without checking with is_some, as it may abort.

Default values: You can use a pattern like returning default when the value is absent.

Mutability: Option can be mutated inside a struct as long as you pass a mutable reference.

  1. CLI and Build Considerations

When testing this logic:

sui move build sui move test

Or simulate via CLI with:

sui client call --function my_function --args 0x1 --package PACKAGE_ID --module my_module

  1. Common Errors to Avoid

Forgetting to check is_some before borrow or extract will abort.

Not initializing Option in struct constructors.

Misunderstanding move semantics: once extracted, the value is gone.

  1. Example Module Snippet

module example::optional_demo { use sui::option::{Self, Option};

public fun new_profile(id: u64): UserProfile {
    UserProfile {
        id,
        nickname: Option::none(),
    }
}

public fun set_nickname(profile: &mut UserProfile, name: String) {
    profile.nickname = Option::some(name);
}

public fun clear_nickname(profile: &mut UserProfile) {
    profile.nickname = Option::none();
}

}

  1. Key Differences from EVM

Unlike Solidity’s null or zero values, Option is a first-class type, ensuring safer handling of possibly absent values. Move forces developers to be explicit, which reduces many categories of bugs.

Using Option properly leads to more robust Move contracts, especially when building on Sui where ownership and state mutation are object-centric and permissioned. If you're integrating this in DeFi tools, wallet logic, or NFTs, it ensures safety and clarity in data modeling. Let me know if you want deeper examples with custom structs or CLI testing commands.

5
Comments
.
Arnold.
Arnold3036
Jul 31 2025, 08:28

Use std::option in Move to handle optional values with Option<T>. Here's how:

1. Basic Usage

use std::option;

// Declare an optional field
struct MyStruct {
    value: option::Option<u64>
}

// Initialize with Some(value) or None
let some_value = option::some(42);
let none_value = option::none();

2. Pattern Matching

public fun get_value(opt: &Option<u64>): u64 {
    if (option::is_some(opt)) {
        option::destroy_some(option::extract(opt)) // Extract and destroy
    } else {
        0 // Default value
    }
}

3. CLI Example

sui move test # Tests should cover Option cases

Key Differences from EVM

  • Explicit Handling: Move forces you to handle None cases (no null pointers)
  • Resource Safety: Option<T> works with resources (unlike EVM's nullable references)

Common Pitfalls

  1. Memory Leaks: Forgetting to destroy_some when extracting
  2. Missing Checks: Not verifying is_some before extraction

Best Practices

  1. Always initialize options explicitly
  2. Use helper functions:
public fun unwrap_or<T: copy>(opt: &Option<T>, default: T): T {
    if (option::is_some(opt)) {
        *option::borrow(opt)
    } else {
        default
    }
}
5
Comments
.
Alya.
Alya-14
Jul 30 2025, 17:32

Use Option<T> from 0x1::option to represent optional values in Sui Move (e.g., Option<u64>, Option<Object>).

use std::option::Option;

// Initialize
let x: Option<u64> = option::some(100);
let y: Option<u64> = option::none();

// Check and extract
if (option::is_some(&x)) {
    let val = option::extract(&mut x); // Consumes the option
    assert!(val == 100);
}

Key rules:

  • Always check is_some() before extract() to avoid aborts.
  • Use borrow() to read without consuming: option::borrow(&x)
  • Never use extract() on none() — it aborts with code 0x1.
  • Common in structs for nullable fields:
    struct User has key {
        id: UID,
        reward: Option<Coin>,
    }
    

Best practice: Prefer Option over sentinel values (e.g., 0 for missing) and always handle both some and none cases to prevent runtime errors.

4
Comments
.
Evgeniy CRYPTOCOIN.
Jul 31 2025, 09:21

Use Option<T> in Move to handle optional values:

  1. Importuse std::option.
  2. Createsome(value) or none().
  3. Unwrapoption::extract(&mut opt) (panics if none).

Example:

let maybe_value: Option<u64> = some(42); // or none()  

Key Notes:
✔ Safer than EVM’s null (explicit handling required).
✔ Use is_none()/is_some() for checks.

Watch For:

  • Unwrapping none() crashes (use option::borrow for safe access).
  • Storage costs for wrapped types.

(Move enforces explicit optionality—no undefined behavior.)

4
Comments
.
Bekky.
Bekky1762
Jul 31 2025, 12:17

1. Core Option<T> Implementation

Move's standard library provides an option module for handling optional values:

Basic Usage

module my_module::optional_data {
    use std::option;
    use sui::object::{Self, UID};

    struct Container has key, store {
        id: UID,
        data: option::Option<u64> // T must have 'drop'
    }

    // Initialize with None
    public fun new(ctx: &mut TxContext): Container {
        Container {
            id: object::new(ctx),
            data: option::none()
        }
    }

    // Set value (Some)
    public fun set_data(container: &mut Container, value: u64) {
        option::fill(&mut container.data, value);
    }

    // Unwrap with default
    public fun get_data(container: &Container): u64 {
        option::extract_with_default(&container.data, 0)
    }
}

2. Key Operations

OperationMove CodeNotes
Create Noneoption::none()Requires T: drop
Create Someoption::some(value)Value consumed
Check existsoption::is_some(&opt)Pure check
Unwrapoption::extract(&mut opt)Panics if None
Safe Unwrapoption::extract_with_default(&opt, default)Fallback value
Destroyoption::destroy_none(opt)Free memory

3. CLI Testing

Create Object with Option

sui client call \
  --package 0xYOUR_PACKAGE \
  --module optional_data \
  --function new \
  --gas-budget 5000000

Interact with Option

# Set value
sui client call \
  --function set_data \
  --args 0xCONTAINER_ID 42 \
  --gas-budget 5000000

# Get value
sui client call \
  --function get_data \
  --args 0xCONTAINER_ID \
  --gas-budget 5000000

4. Safety Patterns

Null Object Pattern

struct NullableNFT has key, store {
    id: UID,
    nft: option::Option<NFT>
}

public fun safe_transfer(
    nullable: &mut NullableNFT,
    recipient: address
) {
    if (option::is_some(&nullable.nft)) {
        let nft = option::extract(&mut nullable.nft);
        transfer::public_transfer(nft, recipient);
    }
}

Memory Management

public fun destroy(container: Container) {
    let Container { id, data } = container;
    object::delete(id);
    option::destroy_none(data); // Required if T doesn't have drop
}

5. Real-World Examples

DeFi (Optional Rewards)

module defi::staking {
    struct RewardState has key {
        id: UID,
        pending: option::Option<Coin<SUI>> // Optional reward
    }

    public fun claim_rewards(
        state: &mut RewardState,
        ctx: &mut TxContext
    ) {
        if (option::is_some(&state.pending)) {
            let reward = option::extract(&mut state.pending);
            transfer::transfer(reward, sender(ctx));
        }
    }
}

NFT (Lazy Minting)

module nft::lazy {
    struct MintTicket has key {
        id: UID,
        metadata: option::Option<vector<u8>> // Set later
    }

    public fun finalize(
        ticket: &mut MintTicket,
        metadata: vector<u8>
    ) {
        assert!(option::is_none(&ticket.metadata), EALREADY_SET);
        option::fill(&mut ticket.metadata, metadata);
    }
}

6. Error Handling

Common Mistakes

ErrorSolution
EOptionAlreadySetCheck is_none before fill
EOptionIsNoneUse extract_with_default
ECannotDestroyEnsure T: drop or call destroy_none

Defensive Programming

public fun safe_unwrap<T: drop>(opt: option::Option<T>): (bool, T) {
    if (option::is_some(&opt)) {
        (true, option::extract(&mut opt))
    } else {
        (false, option::destroy_none(opt))
    }
}

7. Move vs EVM Comparison

FeatureMove Option<T>EVM Approach
Memory SafetyCompile-time checksManual null checks
Gas CostFixed overheadVariable (storage ops)
Type SafetyEnforced by VMDeveloper responsibility

Best Practices

  1. Always initialize options explicitly (none() or some())
  2. Use extract_with_default for fallback values
  3. Clean up with destroy_none when needed
  4. Test edge cases:
    #[test]
    fun test_none_handling() {
        let opt = option::none<u64>();
        assert!(option::is_none(&opt), 0);
    }
    

For production systems:

  • Prefer compile-time checks over runtime Option
  • Use distinct types instead of Option when possible (e.g., struct Active; struct Inactive)
  • Monitor gas usage of nested Option types
3
Comments
.
theking.
Jul 30 2025, 11:10

To represent optional values in Move on the Sui Network, you use the generic enum type Option<T>, which is built into the Move standard library. This is conceptually similar to Option in Rust or Maybe in functional languages. It allows you to store either Some(value) or None, making it ideal for scenarios where a value might be absent—like an unset field or a missing lookup result.

In Sui Move, you use the option module (0x1::option) to work with this pattern. The Option<T> type is defined as:

enum Option<T> has copy, drop, store {
    Some(T),
    None,
}

You use it in structs and functions when a field or return value might be missing. Here’s an example to help you understand how it works practically:

use sui::option::{self, Option};

struct UserProfile has key {
    id: UID,
    nickname: Option<String>,
}

public fun set_nickname(profile: &mut UserProfile, name: String) {
    profile.nickname = option::some(name);
}

public fun remove_nickname(profile: &mut UserProfile) {
    profile.nickname = option::none<String>();
}

public fun has_nickname(profile: &UserProfile): bool {
    option::is_some(&profile.nickname)
}

public fun get_nickname(profile: &UserProfile): &String {
    option::borrow(&profile.nickname)
}

In this example, a UserProfile may or may not have a nickname. You can use option::some(value) to assign a value, option::none<T>() to remove it, option::is_some() to check if it's set, and option::borrow() to access it.

When working with the Sui CLI or SDK, be cautious when reading Option<T> values because the deserialization must match exactly. If using TypeScript SDK or Rust, optional values often map to null or custom wrapper types.

Best Practices:

  • Always match against both Some(_) and None to avoid logic gaps.
  • Use option::unwrap() only when you're certain a value exists, otherwise it will panic (abort).
  • For dynamic data like optional fields in dApps (user bio, metadata), use Option<T> to keep your structs flexible and avoid versioning the whole contract.

This pattern helps reduce null-pointer-like errors and aligns well with secure development practices on Sui. Learn more in the Sui Move stdlib docs on Option.

2
Comments
.
290697tz.
Jul 30 2025, 14:14

In Move, Option is used to represent optional values that may or may not be present. It is defined in the sui::option module and has two variants: some(value) and none(). This type is useful when a field in a struct might be missing or unset, such as an optional nickname or metadata. You can declare an optional value like this: let x: Option = option::some(100); or let y: Option = option::none();. Before accessing the value, always check with option::is_some(&x) to avoid panics. If you need the value, use option::borrow(&x) or option::extract(&mut x)—but extracting will consume the value and leave none. You can use this pattern in smart contracts to safely handle user data, configurations, or upgrade states. In Sui's object-based model, using Option helps maintain clean state transitions and enforce safe access. Always initialize Option fields when creating structs to avoid unexpected behavior. Move's strict typing and lack of null make Option a key tool for writing secure and predictable code.

1
Comments
.

Do you know the answer?

Please log in and share it.