Why can't Rust have a standard way to gradually change formatting rules

Over and over in projects I participate in, we hit the same problem:

"We would like to change formatting rules, but that leads to a huge, hard to review change, that generates merge conflicts with about everything. What do we do?"

And again and again I see people writing custom solutions involving lists of already formatted files, bunch of shell script glue and inconvenient arbitrary lists of steps for contributors, to try to address the issue.

WHY? How can we not see for so long that it is the exactly the same higher-level issue as many other issues Rust community already solved: coordination.

Why can we have a standard smart and well-integrated build system, standard and well-integrated linting system, standard and well-integrated custom build-scripts, standard and well-integrated LSP, C-binding generation, .. and so and so ... but we can't have ... standard and well-integrated way to address this common issue?

The biggest problem with gradually changing formatting is - no matter how it works - that it just needs to be standard. Built-in and doing the right thing automatically. So contributors don't have to read readme's and follow manual step to do the right thing and everything just works out of the box.

1 Like

If you want some standard style, it would be the result of cargo fmt with its default settings. You can encourage contributors to enable format on save on their editor, and check it on CI. Don't let them to change formatting rules.

5 Likes

It seems you (and other people) missed the point.

We do have a project(s) that uses rustfmt.toml and people either run cargo fmt or use LSP/format on save.

But once we (maintainers) want to change the exact settings, it becomes a problem.

Rust has a good solution for "format everything with these rules", but it doesn't have a solution for "we used to format everything with that settings, but now we want to format everything with these settings".

1 Like

IIRC, it's the case that the vscode rust-analyzer extension will (when format-on-save is enabled) run rustfmt only on the files which are saved, not the entire project. So long as you only care for file-level granularity, you can achieve incremental migration by not using cargo fmt (which formats the whole crate) and just using the format on save to rustfmt touched files.

This precludes any sort of testing that format is maintained, unfortunately -- in order to do so you have to either be able to guess which format a given file is using or keep a list somewhere.

However, I think rustfmt and cargo fmt allow using multiple rustfmt.toml, with the closer ones overriding the higher-level ones. So, if you have a large workspace you want to incrementally apply a new format style to, add the rustfmt.toml with the new settings to leaf folders to apply the style there, and gradually move it up. This still requires commits moving the config around to reformat any newly impacted files, but it does allow doing so at least somewhat incrementally.

Assuming that works, anyway; I didn't test that that's actually how it works; I just know that it is the case for other similar things like editorconfig.

1 Like

This will not work in group settings, unfortunately.

I had an idea for a simple system that I think would work seamlessly for everyone by default without any shenanigans: https://www.reddit.com/r/rust/comments/y0fqp9/comment/irugvxl/?context=3

I suspect that I’m in the minority here, but I don’t agree with this premise. Any automatically enforced style rules inevitably force some locally-bad formatting choices in the name of consistency.

Prose authors are supposed to follow the rules of grammar except when violating them improves the text. Similarly, programmers should learn and follow their language’s standard formatting rules most of the time but should also be free to break those rules if doing so makes the code easier to understand.

If you want to enforce style norms, do it as part of the normal code review process. That at least gives the programmer an opportunity to defend their choices.

2 Likes

I think this problem is a self-inflicted one caused by rustfmt's very aggressive unrelenting canonicalization approach. It prioritizes ability to untangle machine-generated one-liner Rust code over being gentle and cooperative with human-written code.

OTOH gofmt allows the same expression to exist in multiple forms without imposing a preference, and keeps whatever already exists in the file (e.g. keeps single-line vs multi-line formatting decision), as long as the formatting is acceptable (e.g. properly indented). It's only rustfmt that insists on forcing changes everywhere, even when no changes are necessary.

So I think Rust really needs a tool like gofmt for human-written code, and rustfmt is not it. There's no such tool yet, and someone needs to write it.


With some git wrangling, it is possible to semi-automatically apply formatting changes gradually only on the code that has changed:

  1. Run rustfmt on the main branch as is, and commit it.
  2. Make the change you want, run rustfmt, and then make a second commit.
  3. Remove the first formatting-only commit, either with git rebase -i and resolving conflicts, or by resetting to unformatted main branch and picking the change with cherry-pick -Xtheirs. In both cases resolve conflicts towards the formatted code. Watch out for orphaned }.

but it would be pretty awesome if a formatting tool could automatically only apply formatting to functions or blocks that have changed, so that you wouldn't need git magic and fight with merge problems when formatting changed too much.

1 Like

I don't want to argue about using rustfmt or not. Been there, done that, got the t-shirt.

The only problem I have is that sometimes the group decides that we actually prefer to tweak the formatting rules (e.g. group_imports = "StdExternalCrate" is a really nice setting), but one large "change formatting settings" PR touching most files is not great.

Exactly. I'd love that. It seems ... so Rust-like. Once you experience it, you don't want to go back.

Also - now that there has been a task group formed around evolving Rust style ... seems like this might be more needed. A project could use previous defaults, and wants to slowly migrate to new recommended settings etc.

Fun fact, rustfmt's not the only formatter for rust. There's also a Prettier plugin for formatting rust code. However, it's far more opinionated than rustfmt is, so you probably wouldn't like it all that much.

What an odd example they chose to highlight. I guess they really like their syntax fixes... even if the fix is invalid code and removed features.

2 Likes

That's what clang-format does, too. And it's great.

Solution which topicstarter talks about is solved with git-clang-format.

This works great in practice. You just try to do git push, see warning, accept it and only parts which were modified are reformatted.

I wonder how hard would it to adopt for rustfmt.

That example is a little weird because of the dropped feature. Syntax fixes are standard for Prettier, so those aren't all that weird. It's also worth noting that while the output code still has three syntax errors even when discounting the now non-existent syntax[1], there is no unambiguous fix for any of them, and by my count, it does successfully fix six syntax errors and a clippy warning. Rustfmt, by design, would choke and die on the first one, and not even begin to attempt to format it.


  1. One attempt to apply an inner attribute as if it were an outer one, and two missing type annotations ↩︎

If only. rustfmt can't handle machine-generated one-liners, because it tends to bail out on long difficult lines. If you need to format machine-generated code, you should use something like prettyplease crate.

rustfmt just has a very OCD approach to dealing with formatting, which is unreasonable and causes pain both for its users and its maintainers. Tyrannically enforcing a single style isn't motivated by any technical reasons (unlike gofmt, rustfmt has extensive configuration options, so there is no "it makes implementation simpler" argument here), it's just a choice of its developers.

1 Like