Is there a clean way to merge Option and Result in a macro


#1

It starts a bit negatively, bare with me.

I have a personal annoyance with how the likes of unwrap() work in Rust.
Specifically that they do not report the line of the code where the issue happened but instead display where panic! was called, and that is inside the Option or Result source code.

I decided to make it panic inside the code through something like map_err(Result only) or unwrap_or_else:

macro_rules! unwrap_opt {
    ($e:expr) => {
        $e.unwrap_or_else(|| panic!("called `Option::unwrap()` on a `None` value"))
    }
}

macro_rules! unwrap_res {
    ($e:expr) => {
        $e.unwrap_or_else(|err| panic!("{}", err))
    }
}

fn main() {
    let crash: Option<i32> = None;
    unwrap_opt!(crash);
}

Is there a way to merge these without introducing much runtime overhead?
Doing “trickery” through implementing Into / From(and than using these appropriately) feels like an unclean solution here.


#2

Macros don’t have access to type information, so no - you can’t distinguish between an option and result in a macro.

However, there is already the expect method on both types, which takes a string as input and prints that on panic. For Results, it also prints the error type. You can do this, at it will Just Work:

macro_rules! unwrap {
     ($x:expr) => ($x.expect(concat!(file!(), ":", line!())));
}

#3

Even though it does not propagate the error from Result, it would be an option.
At moment at least, seems like there is no silver bullet and something has to be sacrificed.


#4

Not sure what “propagate the error” means here, but I think expect(message) of Result shows the error after the given message.



#5

Here’s an idea that uses a trait to turn Option into Result before checking it: https://is.gd/U5TRlu
It’s inspired by the ideas for how try! could be made generic over result carriers. The error type of the new Result is an Option that is used to check if a canned error message should be written or not.


#6

I think you’re getting a message like this:

thread '<main>' panicked at 'called `Option::unwrap()` on a `None` value', ../src/libcore/option.rs:330
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Which, yes, shows the panic inside of the Option library. However, if you run it with RUST_BACKTRACE=1, as that message suggests, then it will display the line number:

$ cat src/main.rs
fn main() {
    let x: Option<i32> = None;
    x.unwrap();
}

$ cargo run
     Running `target/debug/unwrap`
thread '<main>' panicked at 'called `Option::unwrap()` on a `None` value', ../src/libcore/option.rs:330
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Process didn't exit successfully: `target/debug/unwrap` (exit code: 101)

$ RUST_BACKTRACE=1 cargo run
     Running `target/debug/unwrap`
thread '<main>' panicked at 'called `Option::unwrap()` on a `None` value', ../src/libcore/option.rs:330
stack backtrace:
   1:     0x5562fbb2cd80 - sys::backtrace::tracing::imp::write::h3675b4f0ca767761Xcv
   2:     0x5562fbb2ee5b - panicking::default_handler::_$u7b$$u7b$closure$u7d$$u7d$::closure.44519
   3:     0x5562fbb2eac8 - panicking::default_handler::h18faf4fbd296d909lSz
   4:     0x5562fbb2943c - sys_common::unwind::begin_unwind_inner::hfb5d07d6e405c6bbg1t
   5:     0x5562fbb29658 - sys_common::unwind::begin_unwind_fmt::h8b491a76ae84af35m0t
   6:     0x5562fbb2c331 - rust_begin_unwind
   7:     0x5562fbb5d17f - panicking::panic_fmt::h98b8cbb286f5298alcM
   8:     0x5562fbb5d458 - panicking::panic::h4265c0105caa1121SaM
   9:     0x5562fbb28a51 - option::Option<T>::unwrap::h5201650903339503995
                        at ../src/libcore/macros.rs:21
  10:     0x5562fbb289fb - main::h3529877efd0d4ad4eaa
                        at src/main.rs:3         <------------------------- line number reported here
  11:     0x5562fbb2e724 - sys_common::unwind::try::try_fn::h14622312129452522850
  12:     0x5562fbb2c2bb - __rust_try
  13:     0x5562fbb2e1bb - rt::lang_start::h0ba42f7a8c46a626rKz
  14:     0x5562fbb28aa9 - main
  15:     0x7f9e4fa12740 - __libc_start_main
  16:     0x5562fbb288d8 - _start
  17:                0x0 - <unknown>
Process didn't exit successfully: `target/debug/unwrap` (exit code: 101)


If you want to take your From/Into approach, you can still do it without polluting the From/Into traits:
https://is.gd/bLcymn

(EDIT: I think ogeon did something like this already. I didn’t read the replies very carefully.)


#7

Yeah, that’s more or less the same idea.


#8

@tatsuya6502 You are correct.
@withoutboats’s solution is what I was looking for.

I like @ogeon’s solution that it compiles to more efficient code in the end, although of course it doesn’t matter when right before that the application will panick(this is not run in a hot code path many times, it is called only once) :slight_smile:

Why I didn’t want to use the From/Into solution is because of this question:
Now, I ask, why wouldn’t such a macro be added to the std?
Or do you think the benefits(that using this will show the line with the problem without running through the backtrace - probably useful only for small codebases and beginners) are not worth polluting the std?


#9

Related: An unwrap! macro crate