Result y option

En Rust, Result y Option son tipos de enumeración, lo que significa que contienen valores dentro de sus variantes:

enum Option<T> {
  Some(T),
  // existence
  None, // non-existence
}
enum Result<T, E> {
  Ok(T),
  // success
  Err(E), // failure
}

Result

El resultado es un tipo de enumeración, Result<T, E>, donde tanto T como E son genéricos y representan el éxito y el fracaso. Los tipos de resultados se utilizan a menudo en puntos de entrada y controladores:

  • Ok(T): un contenedor de resultados que tuvo éxito y que contiene T

  • Err(E): un contenedor de resultados que ha fallado y que contiene E

Muchos puntos de entrada de contratos se escriben Result<Response, ContractError>, donde Response es la rama Derecha o Éxito, mientras que ContractError es el caso Izquierdo o de falla. Por ejemplo, al observar la base CW20, podemos ver que ejecutar se escribe Result<Response, ContractError>, y la llamada de función que coincide con ExecuteMsg::Transfer sería execute_transfer, que muestra cómo se devuelve el resultado:

pub fn execute_transfer(
  deps: DepsMut,
  _env: Env,
  info: MessageInfo,
  recipient: String,
  amount: Uint128,
) -> Result<Response, ContractError> {
  if amount == Uint128::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)
}

StdResult

También es importante tener en cuenta StdResult, ya que se utiliza con frecuencia en los controladores de consultas y las funciones que invocan.

Por ejemplo, en el contrato de servicio de nombres, puede observar StdResult, que funciona de manera similar a Result, pero carece de una rama de error específica:

#[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()?),
  }
}

Veamos la implementación de 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)
}

Es esencial hacer coincidir o desenvolver los tipos de contenedores con precisión, lo que le permitirá trabajar con los valores contenidos en ellos.

Option

En Rust, no existe el concepto de nil o null, a diferencia de la mayoría de los demás lenguajes de programación convencionales. En cambio, Rust emplea el tipo Opción, que encapsula la noción de presencia o ausencia dentro de un tipo de contenedor.

La option es un tipo de enumeración, con dos variantes:

  • Some(): envuelve un valor interno, al que se puede acceder a través de .unwrap(). -None representa la ausencia de un valor y, a menudo, se utiliza como valor predeterminado o de marcador de posición cuando un valor aún no se conoce o no se puede determinar.

El siguiente fragmento de código es un ejemplo de cómo se puede utilizar la option de Rust para permitir valores opcionales de purchase_price y transfer_price:

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
  pub purchase_price: Option<Coin>,
  pub transfer_price: Option<Coin>,
}

Para los casos del InstantiateMsg anterior, habrá un resultado o nada. Para manejar este escenario, es común usar el operador de coincidencia para hacer coincidir el patrón en los dos casos:

let address = match resolver_read(deps.storage).may_load(key)? {
  Some(record) => Some(String::from( & record.owner)),
  None => None,
};

Si devolver None indicaría un estado de error, convencionalmente se recomienda generar un error en lugar de manejar el valor Ninguno.

Last updated