How are you using rustfmt and clippy?

"AIUI, or at least, from what I remember, rustfmt does this because they wanted a deterministic format that didn't depend on the style of the original code."

Well they nailed it didn't they. Repeat. Deterministic.

Like most things in coding, life and family... my personal tastes are not all that important when it comes time to publish (or have a pleasant meal.)

Being predictable and normative certainly is. Works in dating too. It's a brain thing. Formatting should logically be standardized and easily transformed is desired.

Clearly some collaboration to create CLIPPY configuration sets would tie directly to alternative standards and styles and highlight problem areas. Provided anyone cares about Dev Ops and such it could have benifit.

I have done many bizarre explorations related to natural language code, obfuscation and compaction yet am happy to CLIPPY / format comply within reason. Lol. I am a nice guy that way.

Determinism yes. Narcism no. I vote for a community defined default CLIPPY setting. If I ever voted anyway. I find voting offensive... ... ...

I have zero usage of this. Don't you just open the editor (VSC?) and apply your formatting presets?

I found the few useless warnings in VS to be useless within a design feature and might occur dozens of times.

However a contextually valid one might be buried in the pack.

Regarding the (dev cyclical?) warning review you did? That certainly should be or is a best practice. It is in RAD anyway. (Agile, whatever you use really. When isn't code review a best practice?)

To various degrees the alternatives seem either picky, elitist, unreasonable or unwise.

So... I have to ask... which are the worst, MOST annoying lints? Chuckle. Is there a top ten? Regards, Dave H.

There are two points here both of which are important.

First, the issue of two code modalities in the coding role. I would suggest that after forking you want a highly expanded multi-line view.

However someone experienced with a code base wants a densely packed single line view of the function.

This is about reading mechanics and view paging as much as it is coding. This influences style.

Which leaves the second issue.

Kornel is trying to explain that in some domains style isn't style as such. Layout becomes extremely important and contains both design and conceptual understanding important to the domain expert. Coding isn't the issue.

This implies two rule sets at a minimum.

Excuse my own layout. (Posted via Android Clip Stack.)

1 Like

In Habitat, we use both rustfmt and clippy in our CI (essentially all our devs run rustmft automatically on save, so issues are few and far between and tend to arise only when there are major version changes or an external contributor doesn't have local automation enabled).

There are a lot of useful configurations for rustfmt that are only available in nightly, so despite the fact that we use stable for our actual compilation, we use a nightly version of rustfmt run by this script, with the following configuration (you can see how many of the features are nightly-only). The main caveat is that not every component is present and functional in every nightly release, so we pin to a particular version and bump it occasionally with the help of this matrix: https://rust-lang.github.io/rustup-components-history/.

Clippy is similar. We have scripts to run it on linux and windows in our CI. Both of these are driven by the lists of lints which we've classified for our project as denied, allowed (very few), to-fix (warnings that will move to denied once we fix them the initially), and unexamined (new lints that we haven't agreed on as a team, typically empty). For the most part clippy runs only in CI since issues here are fairly rare once we did the initial pass through our codebase, but they still catch things occasionally that are very useful.

2 Likes

Same here. Absolute consistency of formatting from which there are no exceptions sometimes results in uglier and/or less readable code. I like to have some freedom in how I format my code, and I usually let other contributors do so too as long as their style is also reasonable (it usually is).

I do like to use clippy however, with many more lints than what is enabled by default. I don't take it as holy scripture either, though, and sometimes I'm quite liberal with my #[allow(clippy(…))] annotations. Still, I would like to be reminded whenever I'm about to do something potentially dangerous or expensive that may be right (so the type system doesn't otherwise scream at me for doing it).

3 Likes

Filed an issue with rough mentoring instructions: https://github.com/rust-analyzer/rust-analyzer/issues/1665

4 Likes

This is probably my biggest annoyance with rustfmt -- and with {:#?} actually.

I never want to see

vec![
    1,
    2,
    3,
]

And I want to keep [1, 2, 3] together when they're trivial literals like that, even if it's a long array-of-arrays.

2 Likes

How would you handle this case:

vec![
    vec![9999999999999999999999999999999, 99999999999999999999999999, 999999999999999999999999, 99999999999999999999999999, 9999999999999999999999],
    vec![1, 2, 3, 4, 5]
]

Both inner vectors have the same number of elements, while one clearly line-wraps.

I can understand the desire for representing 2D arrays as tables, but I also understand the difficulty rustfmt faces: it can surely represent things well enough for any given person, but it can't be all things for all people. Hence, compromises. I accept these compromises in favor of not having to worry about formatting anymore.

2 Likes

That statement ignores the fact that rustfmt knows how long the elements are. It could check if all the elements fit on one line, and if they do leave them all as one line, otherwise wrap all the elements for consistency.

4 Likes

That's the thing — it depends. And depends on subtle things that can't be read by measuring number of characters. If the relationship was really very important, and the data had to be structured like this, then I would write it like this:

vec![9999999999999999999999999999999, 99999999999999999999999999, 999999999999999999999999, 99999999999999999999999999, 9999999999999999999999],
vec![                              1,                          2,                        3,                          4,                      5],

but I shall note, this one seems like a very unrealistic edge case, so I don't really expect rustfmt to handle that one. The things I'm worried about are more like long-line versions of:

match v {
  R(r) => ((u.r as i16 - v.r as i16) * (u.r as i16 - v.r as i16)) as u8,
  G(g) => ((u.g as i16 - v.g as i16) * (u.g as i16 - v.g as i16)) as u8,
  B(b) => ((u.b as i16 - v.b as i16) * (u.b as i16 - v.b as i16)) as u8,
}
6 Likes

As another data point, I enabled basically all of clippy's lints and then whitelisted the ones that weren't relevant to my project (like float_arithmetic), but I almost never use rustfmt because I have issues with it similar to @kornel's.

I'd like to be able to just have it automatically run whenever I ask Vim to save, but I'm not willing to compromise on the readability of my code and my preferred coding style (which nightly rustfmt.toml can almost encode) is such a brainlessly habitual thing that I only bother to run rustfmt infrequently (maybe once a month) when I'm feeling willing to burn a known amount of time in git gui reverting the changes I didn't want to catch few if any actual issues.

(Mostly surrounding "I don't have a portrait-orientation monitor. Stop inserting so many newlines." moments on my part.)

2 Likes

FWIW, cases just like this (with less extreme length differences) make up a majority of the test suite of the Javascript app I've been working on my whole career (since the app is basically just displaying a grid of numbers, so of course this is what half of the assertions look like).

If the standard Javascript linters weren't capable of leaving this alone, it's entirely possible I'd have fought to not enforce linting on our codebase, which sounds almost insane when I type that "out loud", but thankfully none of us have ever had to worry about that. So I can definitely empathize with people not using rustfmt over this.

Calling this behavior of rustfmt "deterministic" seems completely wrong btw. You can take the input formatting into account and still be perfectly deterministic in the obvious sense that the same input will consistently produce the same output. So there must be some other rationale behind this, even if it's as simple as technical debt making it impractical to do any better in the short term.

2 Likes

I really love rustfmt. The only thing that irks me is that it doesn't allow additional whitespace:

match foo() {
    Some(v) => ...,
    None    => ...,
    //   ^^^ removed by rustfmt
}
2 Likes

I think the "determinism" is that rustfmt is essentially a function that takes an an AST (which you could consider "pure" code) and returns a styled representation of that code. For any given logically identical program (programs whose ASTs are the same,) rustfmt will return identical formatted files. Additionally, the AST parsed from a rustfmted file should be the same as before it was formatted. This gives idempotency, so we know that running rustfmt any number of times will always produce the same file, because it will never modify the AST.

If you wanted to take input styling into consideration, you no longer can use the AST, at least in a style-agnostic format. You have to come up with a new representation that includes style information. Because it now includes style information, the output AST may be different than the input. You'd now have to worry about proving that your transformation maintains idempotency, or you could get into a situation where running rustfmt multiple times causes it to cycle through different outputs. These problems aren't unsolvable, but I imagine they complicate things.

2 Likes

That's not always true. For example rustfmt preserves some formatting, e.g. the number of line breaks between statements or comments.

1 Like

Take a look at some of the nightly options. I like having things like that lined up which is a large part of why we use nightly just for rustfmt in Habitat.

1 Like

I use both rustfmt and clippy. I'm not entirely happy with rustfmt but I accept it as 'the formatter', with only one changed option, hard_tabs = true, that is because using spaces for indentation is just wrong for accessibility reasons. As the old adage goes, Tabs for Indentation, Spaces for alignment, is dreadfully important especially in regards for sight-impaired users (which we have where I work, though not using Rust here yet) and defaulting to spaces for indentation is just a rather bit evil to such people. Tabs should always be used for indentation, never spaces, because the user can have their tab width set as they wish. Tabs should never be used for alignment, only spaces, even if that means tabbing over to the indentation area then spacing over to align. Following these two rules makes accessibility significantly easier for such classes of users; the defaulting to spaces for indentation does not seem like good research was done.

As for clippy, bit noisy but I've been pleased with what it recommends so far, and silencing it on a case-by-case base is easy enough.

(Ignore my avatar, it's an old gravatar and I need to replace it sometime...)

5 Likes

Tabs for accessibility? That is a new one to me. Why is that exactly?

1 Like

First quick google search result, it's pretty accurate from my work experience as well:


I cannot overstate how important it really is. Ever since working with such people I've become very aware of how much spaces for indentation hurts them and I make an effort to make my code as easy as possible for everyone using it (properly naming variables is so important!), including the visually impaired.

3 Likes

I have always considered TABs as the devil's spawn. Having had to put up with source code scattered all over the place for decades as a result of differing tab widths and tabs and spaces being mixed up.

But what you are saying has me reconsidering my position on that. Especially as we have a nice tool that can ensure that tabs are used properly.

4 Likes

Yeah it can absolutely be a horror when they are mis-used. I've seen my share of code in the past decades that used tabs for alignment, which is perhaps even more horrifying than using spaces for indentation. Right character for the right task.

I've found rustfmt's hard_tab mode working very well so far. I'm still learning rust (probably intermediate at this point, 30 years in C++ with proper ownership handling helped a lot) but so far I haven't noticed anything overly odd with it.