Pre-RFC ~3655 Iteration 2 - Enhancements to Println and Dbg

Posted this RFC to improve println in Zulip. There was some discussion, which was incorporated to make iteration 2 more applicable to dbg

TLDR: we could imitate the python print(f"{a_really_long_name=}" syntax to log variable names alongside values with fewer keystrokes, and this could hopefully be a portable syntax across the println and dbg macros, then we could have consistency in stdin and stderr logging

It's a minor change and I'm not overly invested in it, but could save a many of us a lot of repetitive typing the same thing twice a bunch, so I just want to keep the ball rolling.

Please let me know if you want/need any changes. I will incorporate them!

  • Here is a gist for iteration 2 of the pre-RFC:

Cheers, have a nice weekend

Bion

2 Likes

To avoid potential confusion with the existing {:?} and {:#?} syntax for Debug and pretty-printed Debug output, the shorthand syntax does not support using {x=?} or {x=#?}. Instead, users should use the longer form x={x:?} or x={x:#?}.

That's unfortunate, since this syntax is useful for debugging and in those situations it's more common to print the Debug output.

13 Likes

dbg! doesn't do format strings.

This is a debugging feature, so it should use Debug formatting, not Display.

It prints a variable name, which is developer-facing name. The label can't be localized, can't be customized, and can't even contain spaces or non-ident characters, so it's poorly suited for non-debug user-facing output.

12 Likes

The developer will often know which of the Debug or Display output will be more useful for them.

4 Likes

Sure, but that is not an option in the RFC. The RFC supports only one formatting kind.

Between Display and Debug, the Debug one is much more widely applicable. There are many types in Rust that don't have Display, and Debug is explicitly intended for programmer-facing formatting.

The entire reason for this proposal is saving keystrokes, so even if it could support selection of the formatting trait, the extra required chars would diminish the main benefit of this feature.

10 Likes

let module::nested::variable = "hello";

That's not valid Rust.

To avoid potential confusion with the existing {:?} and {:#?} syntax for Debug and pretty-printed Debug output, the shorthand syntax does not support using {x=?} or {x=#?}. Instead, users should use the longer form x={x:?} or x={x:#?}.

The shorthand syntax also does not support custom formatting specifiers, such as {x=:.2}. In these cases, the longer form x={x:.2} should be used.

This makes the value of this feature for me nil. dbg! already prints the name and value of the variable. The only reason to use explicit println!/eprintln! calls is exactly because I'm not satisfied with dbg!'s choice of formatting.

This syntax is valid in any position where named arguments are currently supported, including formatting strings and write! macros.

Why? It's purely a debugging feature. What reason is there to support it in arbitrary formatting macros? I guess a possible reason is "it makes it easy to implement, because the format string can just be forwarded to format_args!", and that's fair, but the downside is that it can cause unintended bugs in downstream macros which have entirely nothing to do with println-debugging. That smells like a log4j design: let's introduce a reasonably sounding feature and implement it everywhere because why not? and then you deal with the fallout of unintended consequences.

I'm also concerned that the dangling = token can prove to be an issue in the future. It's really a formatting specifier, and it should go with formatting specifiers. Putting it with the spliced expression is weird, and may limit future evolution of string formatting syntax.


Given that this is a purely debugging-oriented feature, imho it's much more reasonable to propose an extension to the syntax of dbg!. It's already used exclusively for print-debugging. All other formatting macros, on the other hand, have a wide range of uses, and I'm very wary of introducing ad hoc extensions to their syntax.

If the issue is "I want to log this text to a file or send over network", perhaps it would be better if dbg! (or a related but different dbg_str!) could write its output to a string rather than stderr, so that the string can be written in whatever way applicable.

13 Likes

If this existed, I would regularly want to use it with the log crate's macros (e.g. in a test where I'm using log's macros for debugging info and want to see a variable's value at a point, since everything already uses log so I want to match everything else). This would require it be implemented in format_args!, which distributes it out to everything else.

If you're that concerned about people accidentally calling it when they don't want to, then it would be easy to write a clippy lint that allows you to prevent it from being used in certain macros.

2 Likes

That's infeasible to do at the level of Clippy, since format strings can be arbitrarily forwarded between macros, which means the set of such potentially erroneous macros would be infinite. One could write a lint which only allows this formatting when passed to log macros or println!/eprintln!, but I don't think Clippy hardcodes crate-specific logic, and most macros would end up calling something like log or println!/eprintln! anyway. Perhaps one could write a custom lint, but that's not a solution at the ecosystem level, and even for specific crates requires enough forward thinking to anticipate this kind of error, which means that at best it would be done after the error happens.

TL;DR: no, Clippy lint is absolutely not a solution to a misuse of formatting specifiers.

One reason is to make the formatting syntax (which is already very complicated) simpler and uniform.

2 Likes

Clippy has lints that are configurable to trigger or not on specific types of values. They don't need hard-code anything for third-party macros, just make it a configurable option for which macros to allow/deny its use on.

How about dbg_fmt! that returns a fmt::Arguments so you can pass the result to all of the format macros? This way the functionality would be isolated to the dbg! family without being restricted to it, and you could write! it wherever you want.

Why should this functionality be isolated to dbg? This is useful for any formatting macro.

1 Like

To avoid potential confusion with the existing {:?} and {:#?} syntax for Debug and pretty-printed Debug output, the shorthand syntax does not support using {x=?} or {x=#?}. Instead, users should use the longer form x={x:?} or x={x:#?}.

That's unfortunate, since this syntax is useful for debugging and in those situations it's more common to print the Debug output.

This makes the value of this feature for me nil. dbg! already prints the name and value of the variable. The only reason to use explicit println!/eprintln! calls is exactly because I'm not satisfied with dbg!'s choice of formatting.

ah my bad, that was a screwup, i just want it to work like python, so I agree with you and deleted those lines from the draft, that's what I get for not carefully reviewing LLM outputs and assuming it didn't dramatically change my intention!

TLDR: let's add {x=<whatever_we_have_now>} just to tack on the variable names

I'll simplify the RFC

Seems like what you're going for is a more portable way to format strings and we could use that in various contexts? @afetisov

I really don't know enough about the implementation details to comment on how it would work or forsee all potential issues, all i wanted was a nice way to include the variable names, and I never use dbg, i always use println, so something that only works in debugging to stderr isn't really hitting my use case. Can you give more advice on how that ought to work?

Ok, I apologize profusely for allowing the errors in the previous draft, no excuse for that, I personally rewrote it, here's a more streamlined version, if this is not doable or I screwed up, please reply and let me know.

"""

Title: Enhanced Variable Printing in Macros

  • Author(s): bionicles
  • Iteration: 3
  • Status: Draft

Abstract: This RFC proposes a new shorthand to include variable names alongside values in Rust, inspired by Python's f-string {x=}.

Motivation: Macro invocations to print variables and their values can be redundant and time-consuming to write and maintain, particularly with long or nested identifiers. The proposed shorthand syntax addresses this issue, making it more convenient to print variables and their values without duplicating variable names.

Currently:

fn main() {
    let a_really_long_name = 42;
    println!("a_really_long_name = {a_really_long_name}"); 
    // Prints "a_really_long_name = 42" to stdout
    dbg!(a_really_long_name);
    // Prints "[src/main.rs:5:5] a_really_long_name = 42" to stderr 
}

Proposed solution: The proposed shorthand allows using {x=} to print the variable x and its value. The macros expand this to the longer form x={x}. This syntax would become valid in Rust string formatting.

Shorthand:

fn main() {
    let a_really_long_name = 42;
    println!("{a_really_long_name=}"); 
    // Prints "a_really_long_name = 42" to stdout
    dbg!(a_really_long_name);
    // Prints "[src/main.rs:5:5] a_really_long_name = 42" to stderr 
}

Alternatives: The discussion mentioned several alternatives to the proposed solution, including using dbg!, log::debug!, or introducing a new macro, such as print_var!. The proposed solution was chosen over these alternatives due to simplicity and familiarity.c

Compatibility: The proposed shorthand syntax does not affect backward compatibility, as it introduces a new syntax that doesn't conflict with existing code. However, it's essential to consider potential confusion or misinterpretation, especially for users unfamiliar with Python f-strings.

Implementation: The implementation of the proposed shorthand syntax involves modifying macro expansion rules to recognize and handle the {x=} syntax. One concern could be if many different macros wind up needing to re-implement this feature. One idea to avoid this is to reuse the logic by making a helper macro.

A key challenge is to correctly parse and expand the shorthand in all cases, including when used in combination with other named arguments, dot accessors, formatting specifiers, etc.

References:

"""

1 Like

Thank you for working on this, and I'd very much like to see = added. That said:

Please don't use LLMs to write Rust contributions, including RFCs. (For this and many other reasons.)

7 Likes

Have you tested this? It doesn't do what you say. The dbg invocation prints:

[src/main.rs:5:5] "a_really_long_name = {a_really_long_name}" = "a_really_long_name = {a_really_long_name}"

It's also a complication for no reason, since dbg!(a_really_long_name) will do what you want, just like you mention a couple of lines after:


I would not only consider backward compatibility, but also forward compatibility. For example if in the future we allow interpolating expressions then this might become a parsing issue since identifier= is the start of a valid expression. Even if the parser might be able to special case it, it could still introduce ambiguity for IDE autocompletitions.

I would instead propose a syntax like {ident:=}, to follow the existing convention of putting formatting specifies after the : character.

3 Likes

Ok, you're right the debug output was off. I tested it in the playground, and updated revision 3 to include a fn main() instead of just code, and corrected the code example and the example output.

The dbg! macro doesn't quite meet the requirements because it only prints to stderr. I print to stdout often and need to show the variable name alongside the value, so constantly need to write variable names twice, making renaming things annoying. It would be great to have an easier way to achieve dbg-like functionality in println! and format!

I would instead propose a syntax like {ident:=}, to follow the existing convention of putting formatting specifies after the : character.

I love this idea! Thank you so much for sharing that!

let a_really_really_long_name = "rust is cool";
println!("{a_really_really_long_name:=}");

Prints

a_really_really_long_name = "rust is cool"

Would it conflict with any existing formatting techniques, like the alignment and width specifications?

Would / should those apply only to values or would they apply to the string with the identifier included?

What should happen in the last 3 examples below? Would we need both "value alignment" and "value and identifier alignment" or is that a YAGNI issue?

fn main() {
    let favorite_species_name = "crabs";
    println!("unaligned {favorite_species_name}");

    println!("=== Width / Alignment, no Identifier ===");
    println!("rightward '{favorite_species_name:>16}'");
    println!("leftward  '{favorite_species_name:<16}'");
    println!("centered  '{favorite_species_name:^16}'");

    println!("=== Width / Alignment with Identifier ===");
    println!("rightward '{favorite_species_name:=>16}'");
    println!("leftward  '{favorite_species_name:=<16}'");
    println!("centered  '{favorite_species_name:=^16}'");
}

I'm reasonably confident we're never going to allow full expressions without at least putting them inside some additional delimiter (e.g. parentheses), if we even allow them at all.

I would really like at least dotted expressions, eg self.ptr, but it's not that big a deal really with all the other options for formatting.

3 Likes