- 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
match
statement is used to handle theResult
andOption
types, 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
Result
for Error Handling: When writing smart contracts, always useResult
to handle potential errors. This ensures that errors are caught and handled gracefully, improving the reliability of your contract. - Leverage
StdResult
in Queries: For standard queries, useStdResult
to simplify your error handling and make your code more consistent with CosmWasm conventions. - Handle
Option
Safely: Avoid using.unwrap()
onOption
unless you are certain that the value exists. Instead, use pattern matching to handle bothSome
andNone
cases. - Pattern Matching: Use Rust’s powerful pattern matching to work with
Result
andOption
types. 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: