Skip to content

Perform Ok-wrapping in try_block macro #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/simple/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn main() {
let result: Result<_, ()> = try_block! {
let a = do_one(x)?;
let b = do_two(a)?;
Ok(b)
b
};

println!("{:?}", result);
Expand Down
60 changes: 53 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/// Macro to make your error-handling blocks (appear) lambda-less
/// and perform Ok-wrapping on the final value.
///
/// #### Before:
/// ```
/// ```ignore
/// let result: Result<T, E> = || {
/// let a = do_one(x)?;
/// let b = do_two(a)?;
Expand All @@ -10,21 +11,66 @@
/// ```
///
/// #### After:
/// ```
/// ```ignore
/// let result: Result<T, E> = try_block! {
/// let a = do_one(x)?;
/// let b = do_two(a)?;
/// Ok(b)
/// b
/// };
/// ```

#[macro_export]
macro_rules! try_block {
{ $($token:tt)* } => {{
let l = || {
$($token)*
};
l()
( || $crate::FromOk::from_ok(
{ $($token)* }
))()
}}
}

/// A type that can Ok-wrap some value, for use in the [`try_block`] macro.
pub trait FromOk {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually avoid the need for this trait with a bit of trickery. Try::from_output is unstable, but you can get to it via try_fold: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0b07b516d97996ac386886ee21d8c561

macro_rules! do_ok_wrapping {
    ($e:expr) => {
        std::iter::empty()
            .try_fold($e, |_, ()| unreachable!())
    };
}

fn main() {
    let _: Option<i32> = do_ok_wrapping!(3);
    let _: std::io::Result<i32> = do_ok_wrapping!(5);
}

(That might make the error messages horrible, though.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can even remove the panic by using the never type instead of unit.

std::iter::empty()
    .try_fold($e, |_, x: !| match x { })

I think this repo is unmaintained so I'm going to fork it, and I'll definitely use your idea.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, nice touch! An uninhabited enum -- no need for ! specifically -- is a great idea.

type Ok;
/// Constructs the wrapped type from an ok value.
fn from_ok(val: Self::Ok) -> Self;
}

impl<T, E> FromOk for Result<T, E> {
type Ok = T;
#[inline]
fn from_ok(val: T) -> Self {
Ok(val)
}
}

impl<T> FromOk for Option<T> {
type Ok = T;
#[inline]
fn from_ok(val: T) -> Self {
Some(val)
}
}

#[cfg(test)]
mod tests {
#[test]
fn parse_sum() {
let result: Result<_, std::num::ParseIntError> = try_block! {
let x = "1".parse::<i32>()?;
let x = "2".parse::<i32>()? + x * 10;
let x = "3".parse::<i32>()? + x * 10;
x
};
assert_eq!(result, Ok(123));
}

#[test]
fn option() {
assert_eq!(
Some(520),
try_block! {
"400".parse::<i32>().ok()? + "20".parse::<i32>().ok()? * "6".parse::<i32>().ok()?
},
);
}
}