Option and Result

Takeaways from ...

code snippets from Logan Smith:
https://www.youtube.com/watch?v=s5S2Ed5T-dc

enum Result<T, E> {
    Ok(T),
    Err(E)
}

enum Option<T> {
    Some(T),
    None
}
  • Result is a generalization of Option.
  • For Result there is a custome Type E if the value is not valid. For Option there is only one represented by None (i.e. there is only one obvious reason that this function could fail)
                result.ok()
                -------------------------> 
Result<T, E>                                Option<T>
                <-------------------------
                opt.ok_or(e)
                opt.ok_or_else(|| f(x))

Operand?

match operand {
    Ok(x) => x,
    Err(e) => return Err(From::from(e)),
}

example:

#![allow(unused)]
fn main() {
struct W(String, String);

fn fallible(_: u8) -> Result<String, E> {
    todo!();
}

impl W {
    fn new(x: u8, y:u8) -> Result<Self, E> {
        // short circuit the function and returns the error
        // if either fallible() fails
        let x = fallible(x)?;
        let y = fallible(y)?;
        Ok(Self(x, y))
    }
    
    // does the same thing
    fn new_v2(x: u8, y:u8) -> Result<Self, E> {
        Ok(Self(fallible(x)?, fallible(y)?))
    }
}

// general:
struct W(Vec<String>);

impl W {
    fn new(v:&[u8]) -> Result<Self, E> {
        let v = v.iter()
                .map( |&x| fallible(x))
                .collect::<Result<Vec<_>, _>>();
        Ok(Self(v?))
    }
}

}

Example 2:

#![allow(unused)]

fn main() {
#[derive(Debug)]
struct Node { /**/ }

#[derive(Debug)]
enum GrandparentError{
    NoParent,
    NoGrandparent
}

// the derive_more::From allows to convert Either GrandparentError
// or IOError into LogGpError
// optionally use thiserror::Error
#[derive(Debug, derive_more::From)]
enum LogGpError {
    GrandparentError(GE),
    IOError(io::Error),
}

impl Node {
    fn parent(&self) -> Option<&Node> {
        todo!();
    }

    fn grandparent(n: &Node) -> Result<&Node, GrandparentError> {
        Ok(
            n.parent().ok_or(GrandparentError::NoParent)?
             .parent().ok_or(GrandparentError::NoGrandparent)?
        )
    }

    fn log_gp(n: &Node) -> Result<(), LogGpError> {
        let g = grandparent(n)?;    // may throw GrandparentError
        fs::write(/**/)?;           // may throw IOError
    }

    // anyhow::Result - dynamically works with any error type...
    // (with a cost ... with its own vtable)
    fn log_gp(n: &Node) -> anyhow::Result<()> {
        let g = grandparent(n)?;    // may throw GrandparentError
        fs::write(/**/)?;           // may throw IOError
    }
}
}

Infallible

#![allow(unused)]
fn main() {
// e.g.
enum Result<bool, Infallible> {
    Ok(bool),        // 2 possible values
    Err(Infallible), // ZERO possible value
}

// e.g.
impl FromStr for Wrapper {
    type Err = Infallible;
    // CAN NOT FAIL!
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(s.into())) 
    }
}
}

References

Study of std::io::Error
https://www.youtube.com/watch?v=s5S2Ed5T-dc