Skip to content

Commit ca5ae21

Browse files
author
Nick Cameron
committed
Update to reflect discussion and recent thinking
1 parent 0023db2 commit ca5ae21

File tree

1 file changed

+41
-23
lines changed

1 file changed

+41
-23
lines changed

text/0000-rustfmt-stability.md

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Some changes would clearly be non-breaking (e.g., performance improvements) or c
1212

1313
The goal is for formatting to only ever change when a user deliberately upgrades Rustfmt. For a project using Rustfmt, the version of Rustfmt (and thus the exact formatting) can be controlled by some artifact which can be checked-in to version control; thus all project developers and continuous integration will have the same formatting (until Rustfmt is explicitly upgraded).
1414

15-
I propose two possible solutions: versioning is handled by Rustfmt (by formatting according to the rules of previous versions), or versioning is handled by Cargo (by treating Rustfmt as a dev dependency).
15+
I propose handling versioning internally in Rustfmt, by formatting according to the rules of previous versions and having users opt-in to breaking changes.
1616

1717

1818
# Motivation
@@ -29,13 +29,13 @@ Rustfmt has a programmatic API (the RLS is a major client), the usual backwards
2929
# Guide-level explanation
3030
[guide-level-explanation]: #guide-level-explanation
3131

32-
If you're using Rustfmt, formatting won't change without an explicit upgrade (i.e., a major version increase). This covers all formatting to all code, subject to the following restriction:
32+
If you're using Rustfmt, formatting won't change without an explicit upgrade (i.e., a major version increase). This covers all formatting to all code, subject to the following restrictions:
3333

3434
* using the default options
3535
* code must compile using stable Rust
3636
* formatting with Rustfmt is *error free*
3737

38-
'Formatting is *error free*' means that when Rustfmt formats a program, it only changes the formatting of the program and does not make any significant changes. I.e., does not change the semantics of the program or any names. This caveat means that we can fix bugs in Rustfmt where the changed formatting cannot affect any users, because previous versions of Rustfmt could cause an error.
38+
'Formatting is *error free*' means that when Rustfmt formats a program, it only changes the formatting of the program and does not change the semantics of the program or any names. This caveat means that we can fix bugs in Rustfmt where the changed formatting cannot affect any users, because previous versions of Rustfmt could cause an error.
3939

4040
Furthermore, any program which depends on Rustfmt and uses it's API, or script that runs Rustfmt in any stable configuration will continue to build and run after an update.
4141

@@ -98,33 +98,39 @@ A common way to use Rustfmt is in an editor via the RLS. The RLS is primarily di
9898

9999
In this section we define what constitutes different kinds of breaking change for Rustfmt.
100100

101+
101102
### API breaking change
102103

103104
A change that could cause a dependent crate not to build, could break a script using the executable, or breaks specification-level formatting compatibility. A formatting change in this category would be frustrating even for occasional users.
104105

105106
Examples:
106107

107108
* remove a stable option (config or command line)
108-
* remove or change some variants of a stable option
109+
* remove or change the variants of a stable option (however, changing the
110+
formatting *behaviour* of non-default variants is *not* a breaking change)
109111
* change public API (usual back-compat rules), see [issue](https://github.com/rust-lang-nursery/rustfmt/issues/2639)
110112
* change to formatting which breaks the specification
111113
* a bug fix which changes formatting from breaking the specification to abiding by the specification
112114

113115
Any API breaking change will require a major version increment. Changes to formatting at this level (other than bug fixes) will require an amendment to the specification RFC
114116

115-
Open question: when and how to update the Rust distribution with such a change. However, I don't expect this to be an issue in the short-run, since we will avoid such changes.
117+
An API breaking change would cause a semver major version increment.
116118

117119
### Major formatting breaking change
118120

119-
Any change which would change the formatting of code which was previously correctly formatted. In particular when run on CI, any change which would cause `rustfmt --write-mode=check` to fail where it previously succeeded.
121+
Any change which would change the formatting of code which was previously correctly formatted. In particular when run on CI, any change which would cause `rustfmt --check` to fail where it previously succeeded.
120122

121123
This only applies to formatting with the default options. It includes bug fixes, and changes at any level of detail or to any kind of code.
122124

125+
A major formatting breaking change would cause a semver minor version increment, however, users would have to opt-in to the change.
126+
123127

124128
### Minor formatting breaking change
125129

126130
These are changes to formatting which cannot cause regressions for users using default options and stable Rust. That is any change to formatting which only affects formatting with non-default options or only affects code which does not compile with stable Rust.
127131

132+
A minor formatting breaking change would cause a semver minor version increment.
133+
128134

129135
### Non-breaking change
130136

@@ -139,27 +145,37 @@ Examples:
139145
* stabilising an option or variant of an option
140146
* performance improvements or other non-formatting, non-API changes
141147

142-
Such changes only require a patch version increment; the Rust distribution can be freely updated.
148+
Such changes only require a patch version increment.
143149

144150

145-
## Proposals
151+
## Proposal
146152

147-
Dealing with API breaking changes and non-breaking changes is trivial so won't be covered here. I have two proposals, one based on managing versioning internally using an 'edition' system, and one based on managing versions externally in Cargo.
148-
149-
### Internal handling
153+
Dealing with API breaking changes and non-breaking changes is trivial so won't be covered here.
150154

151155
* Stabilise the `required_version` option (probably renamed)
152-
* API changes are a major version increment; major and minor formatting changes are a minor formatting increment, BUT major changes are opt-in with a version number, e.g, using rustfmt 1.4, you get 1.0 formatting unless you specify `required_version = 1.4`
153-
* rustfmt supports all versions on the same major version number and the last previous one (an LTS release - open question: is this necessary?), e.g., rustfmt 2.4 would support `1.19, 2.0, 2.1, 2.2, 2.3, 2.4`
156+
* API changes are a major version increment; major and minor formatting changes are a minor formatting increment, BUT major formatting changes are opt-in with a version number, e.g, using rustfmt 1.4, you get 1.0 formatting unless you specify `required_version = 1.4`
157+
* Each published rustfmt supports formatting using all minor versions of the major version number, e.g., rustfmt 2.4 would support `2.0, 2.1, 2.2, 2.3, 2.4`.
158+
* Even if the API does not change, we might periodically (and infrequently) publish a major version increment to end support for old formatting versions.
159+
* The patch version number is not taken into account when choosing how to format.
160+
* if you want older versions, you must use Cargo to get the older version of Rustfmt and build from source.
154161
* internally, `required_version` is supported just like other configuration options
155-
* alternative - the edition version could be specified in Cargo.toml as a dev-dependency/task and passed to rustfmt
162+
* alternative: the version could be specified in Cargo.toml as a dev-dependency/task and passed to rustfmt
156163

157-
This approach adds complexity to Rustfmt (but perhaps no worse than current options). Every bug fix or improvement would need to be gated on either the `required_version` or an unstable option.
158164

159-
On the other hand, all changes are internal to Rustfmt and we don't require changes to any other tools. Users would rarely need to install or build different versions of Rustfmt. Non-breaking changes get to all users quickly.
165+
### Publishing
166+
167+
Rustfmt can be used via three major channels: via Cargo, via Rustup, and via the RLS. To ensure there are no surprises between the different distribution mechanisms, we will only distribute published versions, i.e., we will not publish a Git commit which does not correspond to a release via Rustup or the RLS.
168+
169+
# Drawbacks
170+
[drawbacks]: #drawbacks
171+
172+
We want to make sure Rustfmt can evolve and stability guarantees make that more complex. However, it is certainly a price worth paying, and we should just ensure that we can still make forwards progress on Rustfmt.
160173

161174

162-
### External handling
175+
# Rationale and alternatives
176+
[alternatives]: #alternatives
177+
178+
## External handling
163179

164180
* Major formatting changes cause a major version increment, minor formatting changes cause a minor version increment
165181
- QUESTION - how do we distinguish API breaking changes from major formatting changes?
@@ -175,14 +191,16 @@ On the other hand, all changes are internal to Rustfmt and we don't require chan
175191
Rustfmt would have to maintain a branch for every supported release and backport 'necessary' changes. Hopefully we would minimise these - there should be no security fixes, and most bug fixes would be breaking. Anyone who expects to get changes to unstable Rustfmt should be using the latest version, so we shouldn't backport unstable changes. I'm sure there would be some backports though.
176192

177193

178-
# Drawbacks
179-
[drawbacks]: #drawbacks
194+
## Rationale for choosing internal handling
180195

181-
We want to make sure Rustfmt can evolve and stability guarantees make that more complex. However, it is certainly a price worth paying, and we should just ensure that we can still make forwards progress on Rustfmt.
196+
The internal handling approach adds complexity to Rustfmt (but no worse than current options). Every bug fix or improvement would need to be gated on either the `required_version` or an unstable option.
182197

198+
On the other hand, all changes are internal to Rustfmt and we don't require changes to any other tools. Users would rarely need to install or build different versions of Rustfmt. Non-breaking changes get to all users quickly.
183199

184-
# Rationale and alternatives
185-
[alternatives]: #alternatives
200+
It is not clear how to integrate the external handling with Rustup, which is how many users get Rustfmt. It would also be complicated to manage branches and backports under the external handling approach.
201+
202+
203+
## Other alternatives
186204

187205
Two alternative are spelled out above. A third alternative is to version according to semver, but not make any special effort to constrain breaking changes. This would result in either slowing down development of Rustfmt or frequent breaking changes. Due to the nature of distribution of rustfmt, that would make it effectively impossible to use in CI.
188206

@@ -198,4 +216,4 @@ Other formatters (Gofmt, Clang Format) have not dealt with the stability/version
198216
# Unresolved questions
199217
[unresolved]: #unresolved-questions
200218

201-
We need to decide on internal or external handling of versioning. There are several open questions for the latter (highlighted inline).
219+
Whether we want to specify the version in Cargo instead of/as well as in rustfmt.toml.

0 commit comments

Comments
 (0)