Invocation of line!() and friends

Like described in the manual line!() and friends show the place of the first macro invocation - here at Fuba::new:

fn bust(flag: bool, msg: &str) -> Result<(), Fuba> {
    match flag {
        false => Ok(()),
        true => Err(Fuba::new(msg, file!(), line!(), column!())),
    }
}

fn die(err: &dyn Error) -> () {
    eprintln!("{err}");
    process::exit(13)
}
                                                        
fn main() {                                  
    match bust(true, "kaput") {
        Ok(_) => println!("super"),
        Err(err) => die(&err),
    };
}

Is there any way to avoid this? Or in other words: How do i get the right line?

If I understand, you can call line! in main (as well as in bust), and create a new error. The new error could supersede the first error. Or it could wrap the first error -- then you have nested errors showing both locations.

I don't quite understand that. Or I don't know how to do it yet. In any case, the code shows the error in the line of bust where it is initialized with new - and not where bust is called, i.e. in main. And this behavior is not very practical. At some point, the code is light years away from 'main' - and the lively search begins.

You can create a new error above, and call line! when you create the error. Then you can pass the new error to die.

https://doc.rust-lang.org/1.82.0/core/macro.line.html

Expands to the line number on which it was invoked.

I'm slow on the uptake : fn die(err: &dyn Error, line: u32) -> () and die(&err,line!())? Must I really?

Up to you, there are several ways. My typing is too slow (arm in a sling). I suggest using anyhow -- look at how errors are wrapped/nested. Or wait for others to explain how to do this without a library crate. But note that anyhow is very popular to reduce boilerplate for error handling.

1 Like

That's the most general solution. For many simple cases of error helpers, though, you may be able to just use #[track_caller] and std::panic::Location instead of the file!/line! macros (which still always report the expansion source line).

Or you can capture a backtrace for a fuller picture of the call stack, like is displayed for panics. Crates that provide an "application error" type like anyhow generally capture a backtrace for you when the error is created, since it's the most widely useful tool to see where the error came from.

To use anyhow, you'd return anyhow::Error as your error type instead of Fuba. If the only thing you'll ever do for an error is log and discard it, using an "application error" type is appropriate. "Library error" types are useful when enumerating the potential failure cases is a thing that code can find useful.

4 Likes

Yes , sure. I already had a look on anyhow as well as snafu. It was from an exercise, rolling my own stuff to get a feeling - for the problems etc.

P.S.: #[track_caller] and std::panic::Location are nice, thanks. I have to find my way around this plethora of tools.

Read more
use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct Fuba {
    cause: String,
    file: &'static str,
    line: u32,
    column: u32,
}

impl Fuba {
    fn new(msg: &str) -> Self {
        Self {
            cause: msg.to_string(),
            file: file!(),
            line: line!(),
            column: column!(),
        }
    }
}

impl fmt::Display for Fuba {
    fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
        write!(
            fmtr,
            "{} at {} line {} column {}",
            self.cause, self.file, self.line, self.column
        )
    }
}
            
impl Error for Fuba {
    fn description(&self) -> &str {
        &self.cause
    }
}

fn bust(flag: bool, msg: &str) -> Result<(), Fuba> {
    match flag {
        false => Ok(()),
        true => Err(Fuba::new(msg).into()),
    }
}

fn die(err: &dyn Error) -> () {
    panic!("{err}");
}

fn main() {
    match bust(false, "kaput") {
        Ok(_) => println!("super"),
        Err(err) => die(&err),
    };
    match bust(true, "kaput") {
        Ok(_) => println!("impossible"),
        Err(err) => die(&err),
    };
}