- Docs
- Smart Contracts
- Result and option
Understanding Result and Option
In Rust, Result and Option are powerful enum types used to handle success, failure, and optional values in a type-safe manner. These types are essential when developing smart contracts in CosmWasm, as they help manage various outcomes in contract logic.
Result
The Result type is an enum that represents either success (Ok) or failure (Err). It is commonly used in functions that can return errors, such as contract entry points and handlers.
Definition
enum Result<T, E> { Ok(T), // Represents success and contains a value of type T Err(E), // Represents failure and contains an error of type E}Usage in contracts
In CosmWasm, contract entry points and handlers often return Result<Response, ContractError>. This type signifies that the function can either succeed and return a Response or fail and return a ContractError.
Example: handling a transfer in a CW20 contract
Let's look at how Result is used in the execute_transfer function from a CW20 contract:
pub fn execute_transfer( deps: DepsMut, _env: Env, info: MessageInfo, recipient: String, amount: Uint128,) -> Result<Response, ContractError> { if amount.is_zero() { return Err(ContractError::InvalidZeroAmount {}); } let rcpt_addr = deps.api.addr_validate(&recipient)?; BALANCES.update( deps.storage, &info.sender, |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_sub(amount)?) }, )?; BALANCES.update( deps.storage, &rcpt_addr, |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) }, )?; let res = Response::new() .add_attribute("action", "transfer") .add_attribute("from", info.sender) .add_attribute("to", recipient) .add_attribute("amount", amount); Ok(res)}Key Points
- Ok(T): Represents a successful operation, returning a value of type
T(in this case, aResponse). - Err(E): Represents a failed operation, returning an error of type
E(in this case, aContractError).
The function uses Result to ensure that any errors in the process (such as invalid addresses or insufficient balances) are handled gracefully.
StdResult
StdResult is a type alias commonly used in CosmWasm. It is defined as:
type StdResult<T> = Result<T, StdError>;StdResult is frequently used in query handlers and other functions where the error type is a standard StdError rather than a custom ContractError.
Example: using StdResult in a query handler
In the nameservice contract, the query function returns a StdResult<Binary>:
#[cfg_attr(not(feature = "library"), entry_point)]pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> { match msg { QueryMsg::ResolveRecord { name } => query_resolver(deps, env, name), QueryMsg::Config {} => to_binary(&config_read(deps.storage).load()?), }}Implementation of query_resolver:
fn query_resolver(deps: Deps, _env: Env, name: String) -> StdResult<Binary> { let key = name.as_bytes(); let address = match resolver_read(deps.storage).may_load(key)? { Some(record) => Some(String::from(&record.owner)), None => None, }; let resp = ResolveRecordResponse { address }; to_binary(&resp)}Key points
- StdResult
: A convenient alias for Result<T, StdError>, simplifying error handling in common scenarios. - Pattern Matching: The
matchstatement is used to handle theResultandOptiontypes, making the code more readable and safe.
Option
The Option type in Rust represents the possibility of a value being either present (Some) or absent (None). Unlike many other programming languages that use null or nil, Rust explicitly handles optional values with the Option enum.
Definition:=
enum Option<T> { Some(T), // Contains a value of type T None, // Represents the absence of a value}Usage in contracts:
Option is often used in smart contracts to handle optional parameters or states that may not always have a value.
Example: handling optional prices
Consider a contract that allows optional purchase_price and transfer_price values:
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]pub struct InstantiateMsg { pub purchase_price: Option<Coin>, pub transfer_price: Option<Coin>,}Handling Option with match
To work with Option values, you typically use the match statement:
let address = match resolver_read(deps.storage).may_load(key)? { Some(record) => Some(String::from(&record.owner)), None => None,};Key Points
- Some(T): Wraps an inner value of type
T, which can be accessed using.unwrap()or safely handled using pattern matching. - None: Represents the absence of a value and is often used as a default or placeholder when a value is not present.
Best practices for handling None
If returning None would indicate an error, it’s generally better to handle the error explicitly rather than letting it propagate silently. This can prevent unexpected behavior and make your code more robust.
Practical tips for developers
- Use
Resultfor Error Handling: When writing smart contracts, always useResultto handle potential errors. This ensures that errors are caught and handled gracefully, improving the reliability of your contract. - Leverage
StdResultin Queries: For standard queries, useStdResultto simplify your error handling and make your code more consistent with CosmWasm conventions. - Handle
OptionSafely: Avoid using.unwrap()onOptionunless you are certain that the value exists. Instead, use pattern matching to handle bothSomeandNonecases. - Pattern Matching: Use Rust’s powerful pattern matching to work with
ResultandOptiontypes. This makes your code clearer and safer. - Error Propagation with
?: Use the?operator to propagate errors up the call stack. This keeps your code clean and makes error handling more straightforward.
Further Learning
To deepen your understanding of handling Result and Option in CosmWasm, consider exploring the following resources: