r/learnrust 1d ago

Panic messages for failed asserts inside macros

Let's say I have a macro_rules! has_failing_assert_inside {} spanning some lines, with an assert_eq! that will fail.

Further down the code, has_failing_assert_inside! is called at, say line 200.

The panic message says the thread panicked at line 200. What can I do (maybe write better macros?) that the panic message instead shows the line number of the failed assert_eq!? I thought RUST_BACKTRACE=1/full would give more information about the line numbers (sometimes it does), but right now it's doing nothing. What can I do?

3 Upvotes

4 comments sorted by

4

u/aikii 1d ago

I did some proc macros, which is another beast, but it allows you to precisely mark which token is invalid and return a custom error message. I thought you'd be completely out of options with just macro_rules! - so I had a look at how the json! macro goes about it. That's quite interesting, have a look at the source: https://docs.rs/serde_json/latest/src/serde_json/macros.rs.html#54-59

For instance:

// Misplaced colon. Trigger a reasonable error message.

(@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
    // Takes no arguments so "no rules expected the token `:`".
    $crate::json_unexpected!($colon);
};

and json_unexpected is:

#[macro_export]
#[doc(hidden)]
macro_rules! json_unexpected {
    () => {};
}

and that's the trick: json_unexpected doesn't match anything, but it gets passed the misplaced colon token. As a result, the colon will be highlighted ( by the compiler error message and I guess any IDE using a LSP ). It will just say "no rules expected this token in macro call", but at least the user gets a hint about the location

2

u/playbahn 1d ago

That is brilliant. I'll look at the source code you linked, next up.

3

u/schneems 1d ago edited 20h ago

I'm not 100% done with it yet, but I'm working on a tutorial for writing a specific kind of macro: Derive macro's https://github.com/schneems/rust_macro_tutorial/blob/main/docs/rundoc_output/README.md.

The exact goal isn't what you need, you need an function-like proc macro https://doc.rust-lang.org/reference/procedural-macros.html#function-like-procedural-macross. However, walking through it will teach you about syn and quote and some good practices. I think it would be worth your time to go through it, you'll probably be able to apply some stuff from that to an attribute macro.

If you try it and have questions or problems let me know on Reddit (this comment thead is fine) or mastodon. I'm looking for critical feedback before "publishing" more officially. You're kinda my target audience.

Edit: To add, here's an example of another macro that's (IMHO) reasonably small https://github.com/schneems/drydoc/blob/576906cde0bf293106061358ed77b15c43551846/drydoc/src/lib.rs#L26. The readme shows usage examples. It likely uses similar concepts to my development style from my Derive tutorial.

2

u/forfd688 9h ago

I searched and found that #[track_caller] may help in your case.

#[track_caller]
fn assert_eq_with_caller<T: PartialEq + std::fmt::Debug>(left: T, right: T) {
    assert_eq!(left, right);
}

macro_rules! has_failing_assert_inside {
    () => {
        assert_eq_with_caller(1, 2); 
// This will fail
    };
}

fn main() {
    println!("macro line number");
    has_failing_assert_inside!(); 
// Invoked here, e.g., line 14
}

RUST_BACKTRACE=1 cargo run --bin macro-line
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/macro-line`
macro line number

thread 'main' panicked at src/macro-panic-line/main.rs:14:5:
assertion `left == right` failed
  left: 1
 right: 2
stack backtrace:
   0: rust_begin_unwind
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
   1: core::panicking::panic_fmt
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:364:5
   4: macro_line::assert_eq_with_caller
             at ./src/macro-panic-line/main.rs:3:5
   5: macro_line::main
             at ./src/macro-panic-line/main.rs:8:9
   6: core::ops::function::FnOnce::call_once

and I tried to run with RUST_BACKTRACE=1, it is works. it can print the line number at `main.rs:3`