Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

rustfmt uses 100-char lines by default (and can be configured to fill more), but that's not the problem with it.

The problem is that as soon as a whole statement doesn't fully fit on a single line, rustfmt switches from "horizontal" to "vertical" strategy. It then stops caring how much vertical space it takes, inserts line breaks as often as the style allows, and won't try to use the available line width.

You end up with long runs of finely chopped lines that contain only a single variable or a single call (often with each argument on a separate line too), which looks like having a 20-char line length limit.

It's either fully oneliner `foo().bar().baz()` or fully vertical

    foo()
    .bar()
    .baz();

and you can't have anything in between. It will fight you if you put two calls on the same line.


Good, that is the correct way to format code.

- There are only two strategies and the algorithm to choose between them is trivial for a human to compute. This makes for way better readability, you can reliably predict where the next call/argument is going to be positioned.

- Refactoring becomes easier - moving an argument is now a simple `Line up` editor action.

- Source control diffs become more stable across changes.

...but it's hard to see the benefits on trivially simple examples like the one you presented. Here's a reformatting I did [1] to illustrate this:

Original:

       ReturnType<SomeOther<S, TypeConstructor<Nested<S, T, U>, T>>, U, HmmLetsAdd<V, W>>
Reformatted:

    ReturnType<
        SomeOther<
            S,
            TypeConstructor<
                Nested<S, T, U>,
                T,
            >,
        >,
        U,
        HmmLetsAdd<V, W>,
    >
Vertically verbose, yes, but that hardly matters. The reformatting gave the code visual structure that's made it easy to understand.

[1]: https://news.ycombinator.com/item?id=40427723


Quite the opposite — it blows up the diffs:

You rename a method, now it's a few chars shorter/longer, and some 1-liner call sites can become completely rewritten to the vertical style or vice versa.

You delete a single line, now the expression can fit under a threshold, and the whole block is spaghettified (especially terrible when deleting a struct field).

You wrap code in an if{}, which could have been a clean whitespace-only change for the entire body, but the indentation affects rustfmt's char counts, and it will now force different formatting decisions all over the place.

If you're changing an infallible method to a fallible one, you could have added error handling/fallback on separate lines, keeping changes to the happy path minimal – but not with rustfmt.

For minimal diffs you'd want something with a slack like gofmt, but rustfmt completely overlooked what makes gofmt so great and widely acceptable, and is a misanthropic ruthless canonicalizer instead, which ruins lots of use-cases, including minimal diffs.

Rustfmt's heuristics aren't simple. They're based on char counting of AST nodes, with multiple different limits for different node types, and crossing any threshold can trigger an avalanche of reformatting. There are more complex rules for closures and comments.

These heuristics are a source of formatting inconsistencies, e.g. each match arm is evaluated independently. Even when all match arms contain the same code, they can be formatted wildly differently due to patterns/name lengths pushing different thresholds. Indentation and AST context can make the same expression look differently each time. Such problems largely don't exist in gofmt, so it's not a problem of code formatters, it's a problem of rustfmt specifically.


Well your comment just emphasizes how overdue semantic diffing is, really.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: