Skip to content
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

Attach line & file info to anyhow::Error and anyhow::Context #139

Open
vultix opened this issue Feb 5, 2021 · 6 comments
Open

Attach line & file info to anyhow::Error and anyhow::Context #139

vultix opened this issue Feb 5, 2021 · 6 comments

Comments

@vultix
Copy link

vultix commented Feb 5, 2021

First off, thank you for this wonderful library!

Context

It's often difficult to find where in your code an error occurred. Rust's backtraces help with this, but are often so verbose that it's still difficult.

Proposal

It would be great if you could simply glance at the error output to get file and line information. This could be implemented using #[track_caller] and can be optionally turned on using a feature flag.

I'm not sure what format you'll want, but I'm imagining something like this:

Error: Failed to parse file: hello.txt @ main.rs:7:10

Caused by:
    0: Unable to convert this string into a float: '1.5a' @ main.rs:19:24
    1: invalid float literal @ main.rs:25:32
    
Stack backtrace:
...

I'm willing to make a PR if you like this feature!

Repository owner deleted a comment from mkeedlinger Sep 5, 2021
@drbartling
Copy link

I would like to see this as a debug feature. Off by default for release builds.

@drbartling
Copy link

drbartling commented Sep 23, 2021

For the time being I created this macro for my use:

#[cfg(feature = "file_lines")]
macro_rules! line_leader {
    () => {
        format!("{}:{}", file!(), line!());
    };
}

#[cfg(feature = "file_lines")]
macro_rules! f_context {
    ($msg:literal $(,)?) => {
        format!("{} {}", line_leader!(), $msg);
    };
    ($fmt:expr, $($arg:tt)*) => (
        format!("{} {}", line_leader!(), format!($fmt, $($arg)*));
    )
}
#[cfg(not(feature = "file_lines"))]
macro_rules! f_context {
    ($msg:literal $(,)?) => {
        $msg
    };
    ($fmt:expr, $($arg:tt)*) => (
        format!($fmt, $($arg)*);
    )
}

I use it like this:

            can_error(&arg)
                .context(f_context!("Did error with {:?}", &arg));
cargo run --features=file_lines -- -a arg

src\main.rs:123 Error: This demo failed to execute.

Caused by:
    src\main.rs:456 Did error with "arg"

Repository owner deleted a comment from Rua Feb 21, 2022
@bruwozniak
Copy link

@dtolnay is the https://github.com/dtolnay/anyhow/tree/location branch still planned to be released? Right now we're trying to extract location via the backtraces and as my colleague kindly put it, it's "gnarly".

@laralove143
Copy link

I would like to see this as a debug feature. Off by default for release builds.

i dont agree with this, i think it can be a useful feature for release builds for debugging bugs, i'd rather have this as a separate feature

@rudolphfroger
Copy link

For me a separate feature would also be a good solution. I'd like to track the location, especially also in release builds because it increases the possibility to understand what happened.

@ajantti
Copy link

ajantti commented Sep 1, 2023

As someone coming from C++ where I implemented almost exactly the same thing as Result + anyhow, this gets my vote. In my library I came to depend on it so much it was the immediate first thought for me when I found anyhow. I just implemented my own macro for my library, but I liked the formatting in drbartling's answer so I continued it a bit:

//#[cfg(feature = "file_lines")]
macro_rules! file_line_leader {
    () => {
        format!("{}:{}", file!(), line!())
    };
}

//#[cfg(feature = "file_lines")]
macro_rules! atry {
    ($x:expr) => {
        anyhow::Result::from($x).context(file_line_leader!())?
    };
    ($x:expr, $msg:literal $(,)?) => {
        anyhow::Result::from($x).context(format!("{}: {}", file_line_leader!(), $msg))?
    };
    ($x:expr, $fmt:expr, $($arg:tt)*) => {
        anyhow::Result::from($x).context(format!("{}: {}", file_line_leader!(), format!($fmt, $($arg)*)))?
    };
}
//#[cfg(not(feature = "file_lines"))]
//macro_rules! atry {
//    ($x:expr) => {
//        anyhow::Result::from($x)?
//    };
//    ($x:expr, $msg:literal $(,)?) => {
//        anyhow::Result::from($x).context($msg)?
//    };
//    ($x:expr, $fmt:expr, $($arg:tt)*) => {
//        anyhow::Result::from($x).context(format!($fmt, $($arg)*))?
//    };
//}

So it can be used like this:

atry!(do_stuff());
atry!(do_more_stuff(), "More stuff failed");
let username = atry!(read_username_from_file(), "Couldn't read username from file {}", filename);

With errors like:

Error: sandbox\src\main.rs:112: Couldn't get username

Caused by:
    0: sandbox\src\main.rs:104: File not opened
    1: sandbox\src\main.rs:92
    2: The system cannot find the file specified. (os error 2)

The question is though, should the macro include the ? or not? Usually after checking for error, you want the value so I think for majority of the cases, you'd want the ? anyway, but if you want to return the value, then you have to rewrap the result. And adding two different macros would be just silly. Well, except in my C++ one I had another macro that allows for changing the error kind, but I'll leave this as such for now.

Also I followed the existing code and added it behind a feature, but as mentioned elsewhere, I don't think using features as options is a good thing, because of how they propagate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants