How are you using rustfmt and clippy?

I use rustfmt with nearly stock settings, main one being:

# Super useful to make imports neat
merge_imports = true # not available on stable yet

Clippy is also with stock settings.

am somewhat addicted to anything that says "this is how you can do better!", and even better, if it can do it for me.

3 Likes

For me, acquiring a taste for rustfmt-style seems worthwhile to 'eliminate broad classes of debate', even if I didn't like some of the style when I first looked. I've resisted the temptation to even read about how to customise.

Years ago, I was that person writing style guides etc. I now prefer this problem to be automated-away; freeing up time for malloc-memcpy-golf (most popular sport in the Rust community).

16 Likes

kornel,

I'm do not contribute to projects that enforce rustfmt formatting. I can't stand rustfmt.

That seems rather an extreme view. As much as I may have disagreed with the choices made in a project coding standards I have never seen any that were so awful I would refuse to work on it. I love to hear what you find so objectionable about rustfmt.

My take on it is that in this modern web connected world of Free and Open Source software the project team potentially includes any of the 7 billion people on this planet.

As such there is no place for local variants of style standards. We are all working on the same global project!

I'm all for automate and forget as mentioned above. One less thing to think about and harmoney all around.

10 Likes

In regards to the arbitrary "bikeshedded" style rules, I actually like rustfmt's rules. I happen to use spaces, 1TBS, and trailing commas anyway. I run rustfmt on my projects occasionally and selectively accept changes that don't make readability objectively worse.

My extreme position is mainly about enforcement:

  1. Rustfmt lacks common sense (it's a program with simple rules) and does not take hints from the input source. I don't expect projects that insist on rustfmt to accept #[rustfmt::skip] without arguing, so in the end code will be formatted how rustfmt thinks it should be, even when rustfmt is wrong.
  2. It's annoying when a pull request fails in CI due to formatting style.

My beef with rustfmt is not about the tiny choices. I can work with both tabs and spaces, and various indentation styles. But not with messy code. rustfmt blindly makes big formatting changes, like forced line wrapping and unwrapping, which changes the layout of the code, and destroys visual grouping.

For example, lines may have relationships between them:

match n  {
   1 => (1, one,   ichi),
   2 => (2, two,   ni),
   3 => (3, three, san),
}

and if the lines happen to exceed rustfmt's line limit, it will make a salad out of them:

match n {
   1 => (
      1, 
      one,   
      ichi,
   ),
   2 => (
      2, 
      two,   
      ni,
   ),
   3 => (
      3, 
      three, 
      san,
   ),
}

And I do have this pattern often, because I work with graphics, where I have to have nearly-identical lines for R/G/B or RGB/RGBA/Gray pixel formats (enum variants aren't types, so I can't completely avoid such repetition with generics).

I can't even configure a longer line length in rustfmt, because then it will unwind my iterator chains (where I pay attention to make each line a logical group of operations) into one long spaghetti line.

Similarly with Future chains I pay attention to the rightwards drift:

foo.bar().and_then(|| {
////
});

but rustfmt will happily double it every time:

foo
   .bar()
   .and_then(|| {
////////
   });

For some larger chains that I wrote (and they're large because otherwise passing data down the chain gets verbose and tedious) running rustfmt is like throwing a grenade in them.

15 Likes

Regarding Future chains: we wanted to build a rule for that, and couldn't come up with a sufficiently consistent or easy-to-manually-apply rule for "that's too complicated to go on one line".

Personally, I would say that if you can come up with a consistent rule for when to do that, I'd love to see it implemented in rustfmt.

A sketch of what we experimented with, to suggest a starting point: "allow function calls and single-token arguments, break if you hit line length or if you encounter a function call with a non-trivial expression argument".

We also put a lot of work into the rule that does things like this, to avoid double-indentation:

foo(|bar| {
    // ...
})
1 Like

I don't think it's possible to come up with fixed rules that don't fail, which is why I like gofmt's approach of keeping the general structure of the input code, instead of overriding it with rules.

So my rule would be don't change the number of lines. If a construct is in one line, keep it in one line (but tidy up spaces after commas, etc.) If a construct is in multiple lines, make sure the indentation is even, but don't unwrap them.

5 Likes

This could be modal with both rustfmt and clippy, with the default showing new Rustaceans "Rust's preferred style", thus teaching that style. Experienced Rustaceans like @kornel could use the more nuanced mode that he described in his post.

Edit: I meant both rustfmt and clippy, per the thread title.

@kornel @josh Yeah, this is where I discussed that idea: https://github.com/rust-lang/rustfmt/issues/2263#issuecomment-405370336

I have vim automatically rustfmt my code whenever I save. It's awesome, because no longer is there any debate over code style. Also, I can lazily throw code into the file, then save it and have it look readable automatically. I'm definitely the type to format code manually when I have to, but formatting code by hand is a waste of time when you can have a tool do it for you.

I use Clippy less often.

2 Likes

What about fixed rules that do better than we do today?

Could you give some ideas of the mental heuristics you use?

Would you format foo.bar(a, b).baz(|arg| { on one line?

How about foo.bar(a, b).baz(|arg| { ?

foo.bar(a+b, c+d).baz(|arg| { ?

foo.bar(a).baz(|arg1, arg2| { ?

Arbitrary combinations of the above?

Do some of those change if the expression or expression components get longer?

For example, I'd write:

foo.into_iter().map(|x| {
   x
})

with .into_iter() on the same line, because it is an inconsequential detail. However, I'd write:

foo.into_iter()
    .rev()
    .map(|x| {
        x
    })

or

foo.rev()
    .map(|x| {
        x
    })

because rev() makes a huge difference for an algorithm, and I want it to stand out. So here my formatting is based on perceived importance of a function and how much visual whitespace it gets, rather than the length of its name.


I'd write:

rectangle(
    color,
    top, left,
    width, height,
);

rather than:

rectangle(
    color, 
    top, 
    left, 
    width, 
    height,
);

I find this perfectly fine:

Self {
   color: convert_color_name_to_rgb(),
   x, y, w, h,
}

rather than spread-out in a way that takes up a lot of vertical space without helping readability:

Self {
   color: convert_color_name_to_rgb(),
   x, 
   y, 
   w, 
   h,
}

And the worst case is when there's enough of a line length:

Self { color: convert_color_name_to_rgb(), x, y, w, h }

and it becomes hard to see where the expression for color: ends, and whether x, y, w, h are arguments to the function, or the struct.

With gofmt I can write:

rect   {
    x:x, y:  
  y ,
      w: w, h :h,
      color:    convert_color_name_to_rgb(),
}

and it'll sort it out to:

rect{
    x: x, y: y,
    w: w, h: h,
    color: convert_color_name_to_rgb(),
}
14 Likes

That's only partially what gofmt does though. Go has plenty of built-in language rules about line breaks and formating (if/else constructs, etc) that prevent any ambiguity.

It's true that gofmt leaves long lines alone if it can, but Go is also syntactically a much, much simpler language and has way fewer challenging situations for the formatter. I'm not very impressed with gofmt just because most things it does are so trivial.

clangfmt in comparison is super impressive. It does some black magic in certain situations, for example with macro formatting.

2 Likes

That said, my biggest annoyance with rustfmt is the handling of match statements.

I often insert extra curly braces to make an expression readable, and rustfmt happily screws me over. I think the heuristic that determines "simple" expressions needs to be tuned down a little.

2 Likes

Porting/reimplementing rustfmt on top of rust-analyzer’s concrete syntax trees has been on my todo list since forever, but I might actually get to that “soon”, after wrapping up nixpkgs-fmt project...

I personally also prefer IntelliJ/gofmt style formatting, which enforces patterns but doesn’t mess with line endings. Though I feel like majority of the community is enjoying rustfmt’s one true style. Perhaps it’s a good idea to try to implement an alternative go-style formatter and then ask for community’s consensus?

By the way, if someone’s super eager to do a go-style formatter for Rust and is ready to sink significant amount of time into it (on the order of three work-weeks?), reach out to me for mentoring instructions. I believe that this is doable without too much research as a nice stand-alone project, but it does require time investment. I am not ready to drive that myself right now, but I’d love to teach anybody about what i’ve learned about formatters recently :slight_smile:

9 Likes

"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