Rustfmt and alignment of trailing comments

I often find myself documenting a bunch of lines using trailing comments, e.g.

function_foo();             // call function_foo
function_boo("a string");   // a longer line
f(x);                       // a really short line

However, rustfmt likes to reformat that into this:

function_foo(); // call function_foo
function_boo("a string"); // a longer line
f(x); // a really short line

Which is way worse. With clang-format, you can control this behavior with AlignTrailingComments (see https://clang.llvm.org/docs/ClangFormatStyleOptions.html). Is there no such setting for rustfmt?

1 Like

In general, Rust style is to not use trailing comments, so it wouldn't surprise me if this option wasn't implemented.

This is similar to other problems people have with rustfmt throwing out most input whitespace, for example with column aligned matrices. Most of what keeps me from using rustfmt, at least on projects I do 90% of the work on, is whitespace differences. Here is an alternative hack that rustfmt doesn't mutilate:

function_foo(); //            call function_foo
function_boo("a string"); //  a longer line
f(x); //                      a really short line

I believe its rule is to leave comment content alone, generally. Comparisons to gofmt are common, and I've done nearly nothing with go, but I noticed gofmt tends to leave input whitespace alone in many more cases.

Note that one of the fmt-rfcs/principles.md at master · rust-dev-tools/fmt-rfcs · GitHub is to "Prefer block indent over visual indent" (for a variety of reasons including that visual indent is annoying in screen readers), so it's not surprising to me that this isn't supported.

2 Likes

FWIW, I usually do prefer to use block indentation, except in cases where visual indentation clearly enhances readability. To give a more complex, real world example, below is an excerpt from a current project. Interestingly enough, if you put this code snippet through rustfmt (playground) it completely gives up on formatting the match and doesn't change anything! I'm surprised by that and looking forward to getting this formatting into some "rustfmt protected" projects! :wink:

// Return CharClass for a char
fn char_class(c: char) -> CharClass {
    use CharClass::*;
    match c {
        '\u{0000}'..='\u{0008}' => Control,    // C0 (XML disallowed)
        '\u{0009}'              |              // HT
        '\u{000A}'              |              // LF
        '\u{000B}'              => WhiteSpace, // VT
        '\u{000C}'              => Control,    // FF (C0)
        '\u{000D}'              => WhiteSpace, // CR
        '\u{000E}'..='\u{001F}' => Control,    // C0
        '\u{0020}'              => WhiteSpace, // SPACE

        '\u{007F}'              |              // DEL (C0)
        '\u{0080}'..='\u{009F}' => Control,    // C1 (XML disallowed)
        '\u{00A0}'              => WhiteSpace, // NO-BREAK SPACE (NBSP)

        // Not always (zero) white; shows hypen when line is wrapped.
        // '\u{00AD}'           => Un-         // SOFT HYPHEN,

        // Not white, rendered with a line:
        // '\u{1680}'           => Un-         // OGHAM SPACE MARK

        // Effects subsequent characters in mongolion:
        // '\u{180E}'           => Un-         // MONGOLIAN VOWEL SEPARATOR

        '\u{2000}'..='\u{200A}' => WhiteSpace, // EN QUAD..HAIR SPACE
        '\u{200B}'              |              // ZERO WIDTH SPACE
        '\u{200C}'              => ZeroSpace,  // ZERO WIDTH NON-JOINER

        '\u{2028}'              |              // LINE SEPARATOR
        '\u{2029}'              |              // PARAGRAPH SEPARATOR

        '\u{202F}'              |              // NARROW NO-BREAK SPACE

        '\u{205F}'              => WhiteSpace, // MEDIUM MATHEMATICAL SPACE
        '\u{2060}'              => ZeroSpace,  // WORD JOINER

        '\u{3000}'              => WhiteSpace, // IDEOGRAPHIC SPACE

        '\u{FEFF}'              => ZeroSpace,  // BOM or ZERO WIDTH NON-BREAKING
        '\u{FFFE}'              |              // Bad BOM (not assigned)
        '\u{FFFF}'              => Control,    // Not assigned (invalid)
        _ => Unclassified,
    }
}

Interesting, looks like there are bugs with (or its deliberately ignoring) comments in the middle of patterns. Removing those then results in logging some error[internal] messages https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=db75307bd3692d6ad87e72b86d42c88d

EDIT: oh, I missed one mid-pattern comment, after removing that rustfmt successfully formatted the match.

I'd call this a feature of rustfmt 1.4 that it leaves the original, better formatting alone. Changing it would be breaking WRT format stability, in any case.

What would be a nice improvement in rustfmt 1.5, IMO, would be for it to be able to leave a much wider set of whitespace-only diffs or otherwise "reasonable enough" formatting alone. Some more examples of which might make for an interesting new topic, given the time. One could imagine some sort of heuristic based per-block criteria (how far is current format from rustfmt ideal) and some configurable knob (e.g. 1..10) for how strict rustfmt should be.

I think preferred formatting style is very subjective, however being able to use automated formatting is definitely a plus, especially for cooperating with others.

It would be nice if more vertical alignment and spacing settings were available for rustfmt.

The absence of such settings means that I just don't use rustfmt on my crates. And I haven't found the time to propose the settings or develop something that formats to my liking, so manual formatting for the time being...

What I dream of is something that will just learn from your style, and that doesn't override what you've manually corrected, directly in the editor. Your config file would then be a set of neural network weights...

1 Like

If I was trying to help rustfmt reformat my above function, based on @Nemo157's observation, the simplest change while preserving comments would be to replace all the | patterns with individual match arms (playground)

That leads to another interesting observation: while rustfmt discards the pattern alignment, it actually inserts SPACEs to visually re-align the trailing comments‽ See diff excerpt of what rustfmt does to the above version, below. Isn't it a bit inconsistent for rustfmt to support visual comment alignment in match, but not for the OP's function calls? Reality seems a bit more complicated than just "prefer block indent".

rustfmt 1.4.12-nightly (9f53665 2020-02-10):

-        '\u{0000}'..='\u{0008}' => Control,    // C0 (XML disallowed)
-        '\u{0009}'              => WhiteSpace, // HT
-        '\u{000A}'              => WhiteSpace, // LF
-        '\u{000B}'              => WhiteSpace, // VT
-        '\u{000C}'              => Control,    // FF (C0)
-        '\u{000D}'              => WhiteSpace, // CR
-        '\u{000E}'..='\u{001F}' => Control,    // C0
-        '\u{0020}'              => WhiteSpace, // SPACE
-
-        '\u{007F}'              => Control,    // DEL (C0)
-        '\u{0080}'..='\u{009F}' => Control,    // C1 (XML disallowed)
-        '\u{00A0}'              => WhiteSpace, // NO-BREAK SPACE (NBSP)
+        '\u{0000}'..='\u{0008}' => Control, // C0 (XML disallowed)
+        '\u{0009}' => WhiteSpace,           // HT
+        '\u{000A}' => WhiteSpace,           // LF
+        '\u{000B}' => WhiteSpace,           // VT
+        '\u{000C}' => Control,              // FF (C0)
+        '\u{000D}' => WhiteSpace,           // CR
+        '\u{000E}'..='\u{001F}' => Control, // C0
+        '\u{0020}' => WhiteSpace,           // SPACE
+
+        '\u{007F}' => Control,              // DEL (C0)
+        '\u{0080}'..='\u{009F}' => Control, // C1 (XML disallowed)
+        '\u{00A0}' => WhiteSpace,           // NO-BREAK SPACE (NBSP)

Yeah, sometimes the truth hurts. Us rustfmt skeptics/antagonists need to work together to at least stamp out overly rosy comparisons with gofmt:

:japanese_ogre: gofmt doesn't have this bug! :japanese_ogre:


From https://play.golang.org/p/EgrQFzDUvuU

function_foo()              // call function_foo
function_boo("a string")    // a longer line
f(x)                        // a really short line

After clicking "Format" button:

function_foo()           // call function_foo
function_boo("a string") // a longer line
f(x)                     // a really short line

But "Format" also mutilates my above suggested, hackish rustfmt workaround:

function_foo()           //             call function_foo
function_boo("a string") //   a longer line
f(x)                     //                       a really short line

My style preference is "whatever rustfmt suggests", except in some very specific circumstances.

That said, I have seen a few cases where #[rustfmt::skip] doesn't work because reasons, and some other cases where rustfmt decides to reformat some existing whitespace after an update. It creates some diff noise, but at least the formatting ends up consistent.

Also gofmt thinks that tabs are ok. :roll_eyes:

2 Likes

I was actually thinking of creating a fork-able parallel-universe rust-format (rfcs) kind of specification that defines my personal set of departures from rustfmt, starting by committing a rustfmt-ed branch to the most successful rust open-source I have; but I'm not sure how popular that effort could be. Your example could benefit (as an example) from more chaos (aka entropy) than the identity matrix. Any suggestions for this line of skepticism?

Personally, no I don't really have anything else to add. :slight_smile: Sorry.

In case it wasn't clear, when formatted by rustfmt defaults the identity matrix looks like this:

let transform: [f32; 16] = [
    1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
];

I don't think this is controversial, as far as examples go; The link is far superior to the inlined example above.

2 Likes

So I need a name for my derivative style guide. Would you take the name "Kellum-Backus" seriously if you saw it in a contributor guide? :smiley:

I think one possiblity is working on rustfmt to add more configuration options so it can be more flexible to other styles. For me, a great hurdle is the limited vertical alignment support.

That way there is no need to decide who's style choices are better, but we can enlargen the group of people that can benefit from automated formatting, and can put a rustfmt config in their repo to ease collaboration.

I think it's up to a project author/maintainer to choose what style they like, as they have to read and deal with the code the most and ultimately take responsibility for it. For me the standard configuration of rustfmt produces unreadable ascii art, but I will happily abide with that when filing PR's. It's out of the question in my own projects though. I wouldn't be able to have the same quality standard if I used this formatting.

4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.