How are you using rustfmt and clippy?

I ask this question here under "community" because it's about how the Rust community communicates in actual Rust code.

As a Rust newbie I found rustfmt and clippy after a week of happy hacking my Rust experiments. Oh boy. rustfmt totally rearranged my code, clippy complained like hell about a thousand things.

I guess I could have started tweaking the rules of rustfmt and clippy and bending them to my liking. But I'm lazy, I thought perhaps this the Rust project's way, so I tweaked my code instead to get rid of all the warnings and fix things up according to clippy's suggestions. I was some what amazed that clippy even suggested changing some of my types and even changing syntactic structures altogether.

In other worlds there is endless, useless, debate about source code formatting and style. Everyone has their own opinion. What style guides there are vary from company to company, project to project. I'd be happy if there were only one formatting that a compiler would accept. That would at least save us from the time wasting debate and the effort of having to even think about what style we like.

So my question is? What is everyone else doing with rustfmt and clippy? Are they using the default rules out of the box. Or are they tweaking around making their own house style?

3 Likes

I am (slowly) migrating to rustfmt with the following config:

max_width = 79
use_small_heuristics = "max"

I still have not got on the Clippy train. In principle, it's a good idea, but I need to sit down and go through the considerable number of lints to enable/disable ones I agree/disagree with. There are lots in the default set that I don't agree with unfortunately. (If I were starting a greenfield project in collaboration with others, I'd probably take the time to sit down and whitelist some initial set of lints.)

3 Likes

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

BTW, I love gofmt. It doesn't actually do what everyone assumes it does. gofmt preserves single-line expressions as single-line, and preserves multi-line expressions as multi-line. It just makes existing indentation neater, preserving original style and layout of the code. Rustfmt OTOH is a bulldozer that destroys everything.

8 Likes

I enforce rustfmt and clippy in CI in my projects, both with stock settings. I don't personally find any of Clippy's default lints objectionable enough to turn off globally, but I do disable them locally where relevant. If nothing else, the fact that there's an explicit annotation disabling a specific lint around a function gives people a heads up that there's something interesting to be aware of.

14 Likes

Yes, this is my primary complaint about rustfmt. I don't see it changing any time soon, so I decided to just swallow the pill. 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.

3 Likes

I use rustfmt and clippy with the default settings, and I'm pretty happy with them - I definitely prefer rustfmted code over non-rustfmted code, and I've never had any reason to complain about it destroying my formatting.

17 Likes

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).

19 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.

11 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.

18 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.

6 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: Limit chain length by number of calls as well as width · Issue #2263 · rust-lang/rustfmt · GitHub

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(),
}
18 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:

11 Likes