Cfg annotation for debugging

I have some very detailed outputs for my code, which I want to run only during debugging.

I started with #cfg(debug_assertions) but this also runs when I do cargo run.

So I created a feature "my_debug" which I added only in the debug configuration and changed the code accordingly: #[cfg(all(debug_assertions, feature = "my_debug"))].

This actually works fine when running and debugging the code, but has the drawback, that the code is grayed out. This has a number of drawbacks, e.g. left out during renaming.

So I added my_debug to the settings in rust-analyzer.cargo.features.
Now the code is not grayed out anymore, but now I get errors.

Only in my output I use the crate utils.rs.

#[cfg(all(debug_assertions, feature = "debug"))]
use crate::utils::U64Ext;

This compiles and runs fine, BUT the analyzer now marks the use statement as 'unused' and gives me a warning. When I later use that crate it shows me an error, because it is not imported.

Furthermore, now the feature my_debug is also used during test, which I do not want.

I can remove the cfg-directive, but then I get warnings during actual compilation.

So as a last resort I need to qualify the crate on each statement, which I find a bit cumbersome.

Any idea how I can resolve this?

maybe you need a proper logging facility with configurable filtering functionality, such as log and tracing.

you can use cargo run --release

you can use the cfg-if crate to reduce some clutter.

Alternative spelling: you're logging out different parts of your program with different levels of verbosity, which you'd like to be able to configure on a bit more granular of a level than the one provided by a fairly black-and-white #[cfg(debug_assertions)] annotation and/or a feature flag.

It's a fairly common problem, with a set of fairly common solutions. You could spend a bunch of time reinventing your own proverbial wheel, or just use a crate like log or tracing instead.

Thanks for the hint on the log crates. That can simplify the output indeed.

But I am not sure if that solves the issue with my conditional structs, which contain fields that I do not want in the release build.


This is a struct in my program:
/// Single Step when run, records the state before to identify loops
#[derive(Clone)]
struct StepExpanding {
    for_symbol_state: i16,
    tape_before: TapeType,
    high_bound_before: BitType,
    low_bound_before: BitType,

    #[cfg(all(debug_assertions, feature = "my_debug"))]
    pub text: [char; 3],
}

This is not included in the release build, but should be available during debugging. I do not mind if it is included when using cargo run actually, the problem is, that tests fail for verbose output.

This particular bit:

and this one:

aren't exactly the same issues.

I'd consider extracting them into some static variables instead, using static_init or the like.

In the actual code then, "macro gate" the particularly verbose parts:

use log::{error, warn, info, debug, trace};
use env_logger::{Builder as Logger, Env}; // 0.11.8

fn setup() {
    let env = Env::default()
        // change to "trace" to "populate fields"
        .default_filter_or("debug"); 
    Logger::from_env(env).init();
}

fn main() {
    setup();
    // use any/all of these
    error!("error here");
    warn!("warn here");
    info!("info here");
    debug!("debug here");
    // for complex operations:
    trace!("trace here: {}", {
        println!("<populating struct fields>");
        std::thread::sleep(std::time::Duration::from_secs(5));
        println!("<struct fields populated>");
        "done"
    });
}

That's strange, and not a little worrying! You should probably see if that's a bug or change. If you switch to RustRover, you'll see that it keeps performing the refactoring operations on disabled code as expected (for ex. if a #[cfg(any())] temporarily disables a block or if a feature is not enabled), and it includes those parts when you search where some item is used. You can also choose to keep the syntax highlighting but with a different background or some effect like borders or strikethrough.

For debugging, I'm using a very basic technique: I define const VERBOSE bools that I change manually when I need to display more information, which often implies additional instructions than just the println!. All those debug parts are simply behind if VERBOSE {...}s. I usually use one constant per function, implementation, or module during the development, and if I want a more fine-grained control, I can copy the declaration more locally to override the outer one, like inside a particular block. I've never felt the need for anything else so far. It could be a complement to the config system you're using.