Skip to content

Commit

Permalink
Enhance docs and add a few methods for option.sw (FuelLabs#3322)
Browse files Browse the repository at this point in the history
A lot of this is copied from
https://doc.rust-lang.org/src/core/option.rs.html#1864

Now that we have `forc-doc`, we want to spend some time enhancing all of
our standard library docstrings.

There are a few more methods we can implement. I attempted `transpose()`
but faced an [issue](FuelLabs#3321).
There are all the Boolean methods such as `and` and `or` as well.

- [x] Write tests for `unwrap_or` and `ok_or`.
- [x] Enhance tests for `Option` in general

Co-authored-by: JC <[email protected]>
Co-authored-by: Emily Herbert <[email protected]>
Co-authored-by: Toby Hutton <[email protected]>
  • Loading branch information
4 people authored Nov 23, 2022
1 parent f581909 commit a7fa7d9
Show file tree
Hide file tree
Showing 5 changed files with 429 additions and 42 deletions.
2 changes: 1 addition & 1 deletion sway-lib-std/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ dep error_signals;
dep logging;
dep revert;
dep assert;
dep option;
dep result;
dep option;
dep alloc;
dep contract_id;
dep constants;
Expand Down
183 changes: 172 additions & 11 deletions sway-lib-std/src/option.sw
Original file line number Diff line number Diff line change
@@ -1,18 +1,88 @@
//! Error handling with the `Option` type.
//! Optional values.
//!
//! [`Option<T>`][`Option`] is the type used for representing the existence or absence of a value. It is an enum with the variants, [`Some(T)`], representing
//! some value, and [`None()`], representing
//! no value.
//! Type [`Option`] represents an optional value: every [`Option`]
//! is either [`Some`] and contains a value, or [`None`], and
//! does not. [`Option`] types are very common in Sway code, as
//! they have a number of uses:
//!
//! * Initial values where [`None`] can be used as an initizlier
//! * Return value for otherwise reporting simple errors, where [`None`] is
//! returned on error
//! * Optional struct fields
//! * Optional function arguments
//!
//! [`Option`]s are commonly paired with pattern matching to query the presence
//! of a value and take action, always accounting for the [`None`] case.
//!
//! ```
//! fn divide(numerator: u64, denominator: u64) -> Option<u64> {
//! if denominator == 0 {
//! Option::None
//! } else {
//! Option::Some(numerator / denominator)
//! }
//! }
//!
//! fn call_divide() {
//! // The return value of the function is an option
//! let result = divide(6, 2);
//!
//! // Pattern match to retrieve the value
//! match result {
//! // The division was valid
//! Option::Some(x) => std::logging::log(x),
//! // The division was invalid
//! Option::None => std::logging::log("Cannot divide by 0"),
//! }
//! }
//! ```
//!
//! # Method overview
//!
//! In addition to working with pattern matching, [`Option`] provides a wide
//! variety of different methods.
//!
//! ## Querying the variant
//!
//! The [`is_some`] and [`is_none`] methods return [`true`] if the [`Option`]
//! is [`Some`] or [`None`], respectively.
//!
//! [`is_none`]: Option::is_none
//! [`is_some`]: Option::is_some
//!
//! ## Extracting the contained value
//!
//! These methods extract the contained value in an [`Option<T>`] when it
//! is the [`Some`] variant. If the [`Option`] is [`None`]:
//!
//! * [`unwrap`] reverts
//! * [`unwrap_or`] returns the provided default value
//!
//! [`unwrap`]: Option::unwrap
//! [`unwrap_or`]: Option::unwrap_or
//!
//! ## Transforming contained values
//!
//! These methods transform [`Option`] to [`Result`]:
//!
//! * [`ok_or`] transforms [`Some(v)`] to [`Ok(v)`], and [`None`] to
//! [`Err(err)`] using the provided default `err` value
//!
//! [`Err(err)`]: Err
//! [`Ok(v)`]: Ok
//! [`Some(v)`]: Some
//! [`ok_or`]: Option::ok_or
library option;

use ::convert::From;
use ::result::Result;
use ::revert::revert;

/// `Option` is a type that represents either the existence of a value ([`Some`]) or a value's absence
/// ([`None`]).
/// The `Option` type. See [the module level documentation](self) for more.
pub enum Option<T> {
/// Signifies the absence of a value
/// No value.
None: (),
/// Contains the value
/// Some value of type `T`.
Some: T,
}

Expand All @@ -23,31 +93,122 @@ impl<T> Option<T> {
/////////////////////////////////////////////////////////////////////////
// Querying the contained values
/////////////////////////////////////////////////////////////////////////
/// Returns `true` if the result is [`Some`].
/// Returns `true` if the option is a [`Some`] value.
///
/// # Examples
///
/// ```
/// let x: Option<u32> = Option::Some(2);
/// assert(x.is_some());
///
/// let x: Option<u32> = Option::None;
/// assert(!x.is_some());
/// ```
pub fn is_some(self) -> bool {
match self {
Option::Some(_) => true,
_ => false,
}
}

/// Returns `true` if the result is [`None`].
/// Returns `true` if the option is a [`None`] value.
///
/// # Examples
///
/// ```
/// let x: Option<u32> = Option::Some(2);
/// assert(!x.is_none());
///
/// let x: Option<u32> = Option::None;
/// assert(x.is_none());
/// ```
pub fn is_none(self) -> bool {
match self {
Option::Some(_) => false,
_ => true,
}
}

/////////////////////////////////////////////////////////////////////////
// Getting to contained values
/////////////////////////////////////////////////////////////////////////
/// Returns the contained [`Some`] value, consuming the `self` value.
///
/// Because this function may revert, its use is generally discouraged.
/// Instead, prefer to use pattern matching and handle the [`None`]
/// case explicitly.
/// case explicitly, or call [`unwrap_or`].
///
/// [`unwrap_or`]: Option::unwrap_or
///
/// # Reverts
///
/// Reverts if the self value equals [`None`].
///
/// # Examples
///
/// ```
/// let x = Option::Some(42);
/// assert(x.unwrap() == 42);
/// ```
///
/// ```
/// let x: Option<u64> = Option::None;
/// assert(x.unwrap() == 42); // fails
/// ```
pub fn unwrap(self) -> T {
match self {
Option::Some(inner_value) => inner_value,
_ => revert(0),
}
}

/// Returns the contained [`Some`] value or a provided default.
///
/// [`unwrap_or`]: Option::unwrap_or
///
/// # Examples
///
/// ```
/// assert(Option::Some(42).unwrap_or(69) == 42);
/// assert(Option::None::<u64>().unwrap_or(69) == 69);
/// ```
pub fn unwrap_or(self, default: T) -> T {
match self {
Option::Some(x) => x,
Option::None => default,
}
}

/////////////////////////////////////////////////////////////////////////
// Transforming contained values
/////////////////////////////////////////////////////////////////////////
/// Transforms the `Option<T>` into a [`Result<T, E>`], mapping [`Some(v)`] to
/// [`Ok(v)`] and [`None`] to [`Err(err)`].
///
/// [`Ok(v)`]: Ok
/// [`Err(err)`]: Err
/// [`Some(v)`]: Some
/// [`ok_or`]: Option::ok_or
///
/// # Examples
///
/// ```
/// let x = Option::Some(42);
/// match x.ok_or(0) {
/// Result::Ok(inner) => assert(inner == 42),
/// Result::Err => revert(0),
/// }
///
/// let x:Option<u64> = Option::None;
/// match x.ok_or(0) {
/// Result::Ok(_) => revert(0),
/// Result::Err(e) => assert(e == 0),
/// }
/// ```
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
match self {
Option::Some(v) => Result::Ok(v),
Option::None => Result::Err(err),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
library data_structures;

use core::ops::*;
use std::hash::sha256;

/////////////////////////////////////////////////////////////////////////////
// Data Structures Used in in the Tests
/////////////////////////////////////////////////////////////////////////////
pub struct MyStruct {
x: u64,
y: u64,
}

pub enum MyEnum {
X: u64,
Y: u64,
}

impl Eq for MyStruct {
fn eq(self, other: Self) -> bool {
self.x == other.x && self.y == other.y
}
}

impl Eq for MyEnum {
fn eq(self, other: Self) -> bool {
match (self, other) {
(MyEnum::X(val1), MyEnum::X(val2)) => val1 == val2,
(MyEnum::Y(val1), MyEnum::Y(val2)) => val1 == val2,
_ => false,
}
}
}

impl Eq for (u64, u64) {
fn eq(self, other: Self) -> bool {
self.0 == other.0 && self.1 == other.1
}
}

impl Eq for [u64; 3] {
fn eq(self, other: Self) -> bool {
self[0] == other[0] && self[1] == other[1] && self[2] == other[2]
}
}

impl Eq for str[4] {
fn eq(self, other: Self) -> bool {
sha256(self) == sha256(other)
}
}

/////////////////////////////////////////////////////////////////////////////
// Error
/////////////////////////////////////////////////////////////////////////////
pub enum Error {
BoolError: bool,
U8Error: u8,
U16Error: u16,
U32Error: u32,
U64Error: u64,
StructError: MyStruct,
EnumError: MyEnum,
TupleError: (u64, u64),
ArrayError: [u64; 3],
StringError: str[4],
}

impl Eq for Error {
fn eq(self, other: Self) -> bool {
match (self, other) {
(Error::BoolError(val1), Error::BoolError(val2)) => val1 == val2,
(Error::U8Error(val1), Error::U8Error(val2)) => val1 == val2,
(Error::U16Error(val1), Error::U16Error(val2)) => val1 == val2,
(Error::U32Error(val1), Error::U32Error(val2)) => val1 == val2,
(Error::U64Error(val1), Error::U64Error(val2)) => val1 == val2,
(Error::StructError(val1), Error::StructError(val2)) => val1 == val2,
(Error::EnumError(val1), Error::EnumError(val2)) => val1 == val2,
(Error::TupleError(val1), Error::TupleError(val2)) => val1 == val2,
(Error::StringError(val1), Error::StringError(val2)) => sha256(val1) == sha256(val2),
_ => false,
}
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,21 @@
script;

use std::option::*;
use std::revert::revert;
dep data_structures;
dep tests;

use tests::*;

fn main() -> bool {
test_some();
test_none();
test_unwrap_some();
test_bool();
test_u8();
test_u16();
test_u32();
test_u64();
test_struct();
test_enum();
test_tuple();
test_array();
test_string();

true
}

fn test_some() {
let o = Option::Some(42u64);

if (!o.is_some() || o.is_none()) {
revert(0);
}
}

fn test_none() {
let o = Option::None::<()>();

if (o.is_some() || !o.is_none()) {
revert(0);
}
}

fn test_unwrap_some() {
let o = Option::Some(42u64);

let u = o.unwrap();
if (u != 42) {
revert(0);
}
}
Loading

0 comments on commit a7fa7d9

Please sign in to comment.