-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document layout SemVer compatibility. #12169
Conversation
src/doc/src/reference/semver.md
Outdated
|
||
It is a breaking change to change the alignment, layout, or size of a type that was previously well-defined. | ||
|
||
In general, nominal types that use the [the default representation] do not have a well-defined alignment, layout, or size. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What distinction is "nominal types" meant to make, here? Unnamed types, like tuples, that use the default representation (which is the only option we provide for them as far as I know) also don't have a well-defined alignment, layout, or size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was mostly trying to emphasize that this discussion was around types that accept the #[repr]
attribute. However, it is not important and I dropped the word.
src/doc/src/reference/semver.md
Outdated
|
||
Some examples of changes that are not a breaking change are (assuming no other rules in this guide are violated): | ||
|
||
* Adding, removing, or changing fields of a default representation struct, union, or enum. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is often going to be a breaking change, and while "assuming no other rules" above does cover that, leading with this one still seems potentially misleading. Should this say something along the lines of "assuming the changes do not otherwise break code using the types, such as code initializing an object of the type".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, yea, I was reluctant to try to repeat all the caveats, and defer to the comment above about assuming no other rules are broken. But you are correct that reading this on its own is very misleading. I have added some caveats, with links to the sections that explain them in more detail.
src/doc/src/reference/semver.md
Outdated
Some examples of changes that are not a breaking change are (assuming no other rules in this guide are violated): | ||
|
||
* Adding, removing, or changing fields of a default representation struct, union, or enum. | ||
* Adding variants to a default representation enum. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, this one is often going to be breaking, in the absence of non_exhaustive
or an existing private variant. I don't want us to redundantly cover potentially breaking changes repeatedly, but I feel like this could use a bit of qualification (e.g. "Adding variants to a default representation enum, if the enum already has private variants or uses non_exhaustive
")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added qualification here, too, with links.
src/doc/src/reference/semver.md
Outdated
Note that this may be a breaking change since it may change the size and alignment of the type. | ||
Care should be taken in this case. | ||
Public fields may be added if there are private fields, or it is `non_exhaustive`, and the addition does not alter the layout of the other fields. | ||
* Adding variants to a `repr(C)` enum. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is breaking if not non_exhaustive
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, added the caveat.
src/doc/src/reference/semver.md
Outdated
* Adding, removing, or changing private fields of a `repr(C)` struct, union, or enum. | ||
Note that this may be a breaking change since it may change the size and alignment of the type. | ||
Care should be taken in this case. | ||
Public fields may be added if there are private fields, or it is `non_exhaustive`, and the addition does not alter the layout of the other fields. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Private fields are also breaking if there aren't yet any private fields and the type isn't non_exhaustive
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, added some caveats.
Thanks for the review! I decided to go the route of splitting each point into a separate section. The I was wondering if you had any comments on the two |
@joshtriplett Just checking in to see if you'll be able to review the updates. |
src/doc/src/reference/semver.md
Outdated
In these cases, it may be safe to make changes to the types, though care should be exercised. | ||
For example, types with private fields that do not otherwise document their alignment, layout, or size guarantees cannot be relied upon by external crates since the public API does not fully define the alignment, layout, or size of the type. | ||
|
||
A common example where a type with *private* fields is well-defined is a type with a single private field with a generic type, using `repr(transparent)`, and which is documented as being transparent to the generic type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"documented as being transparent" is ambiguous since rustdoc shows #[repr(transparent)]
on some types even when they have private fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking a look! My intent wasn't that #[repr(transparent)]
alone in the documentation doesn't mean it is "documented". I was pointing towards the actual text of the documentation discussing the guarantees it provides (for example, the linked UnsafeCell has an entire section about memory layout). If there is a private field, rustdoc by itself doesn't provide enough information to know what type the field is, and thus there is no way to know what the layout of the type is. The prose needs to explicitly indicate that the private field is just T, and that it is transparent to it (or some other explicit guarantee). I adjusted the text a little to try to make that clearer.
☔ The latest upstream changes (presumably #12339) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, I'm strongly in support and I learned a lot from reading it. Thanks for putting it together!
I look forward to updating our existing cargo-semver-checks lints to point to the new sections, as well as adding new lints for things we don't already check.
If you have a moment to spare, I'd love your guidance on a few edge cases related to programmatically determining whether alignment changes are breaking or not — I put details inside PR comments in the relevant spots.
src/doc/src/reference/semver.md
Outdated
#### Major: Changing the value N of `repr(packed(N))` if that changes the alignment or layout | ||
|
||
It is a breaking change to change the value of N of `repr(packed(N))` if that changes the alignment or layout. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tangent: might be nice to expand the repr(packed)
nomicon docs here to mention that repr(packed)
can take an argument. This is the first time I learned that's the case, and I've probably done a bit more digging through the reprs docs than average Rustaceans.
The Type Layout page's section on alignment modifiers of the reference also mentions it, but that's not the natural first place to go look up how repr(packed)
can be used.
If the value `N` is lowered below the alignment of a public field, then that would break any code that attempts to take a reference of that field. | ||
|
||
Note that some changes to `N` may not change the alignment or layout, for example increasing it when the current value is already equal to the natural alignment of the type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tangent: any recommendations on how to write a high-quality, precise lint for this in cargo-semver-checks? Would love to lint this in a way that doesn't produce false-positives, but I don't feel I understand the nuances yet.
For example, if repr(packed(N))
is used on a type containing repr(Rust)
values, how can we best (ideally, programmatically) figure out whether we've hit the "N lowered below alignment of public field" criterion if said fields are repr(Rust)
? I'm confused because based on these additions I think the repr(Rust)
values might not have well-defined layout or alignment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Computing the alignment and layout is a nontrivial thing to do. It is target-specific, and I think it would be a huge effort to duplicate everything the compiler does. Some things, like ctest2
create a Rust file with the structure and some assertions to check alignment and layout. The memoffset
crate and the standard library functions like align_of_val
can be used for gaining some information about a structure. -Zprint-type-sizes
can be used to print the layout information (but only for human-readable debugging).
Overall, I don't think it is feasible for cargo-semver-checks to compute the actual layout and alignment.
For this specific clause, I probably wouldn't have any automated checks to determine if alignment changes. I would just say "any change to N is breaking".
You are correct that repr(Rust)
does not have a well-defined layout or alignment. That means anything that contains those repr(Rust)
types also does not have a well-defined layout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that's super useful and makes sense.
You are correct that
repr(Rust)
does not have a well-defined layout or alignment. That means anything that contains thoserepr(Rust)
types also does not have a well-defined layout.
Love this! In fact, I think having that statement ("anything that contains repr(Rust)
types does not have a well-defined layout") in the doc as an additional clarification might be valuable from a pedagogical point of view. Just my 2 cents.
Thanks for taking a look @obi1kenobi! |
src/doc/src/reference/semver.md
Outdated
* Adding variants to a `repr(C)` enum, if the enum uses `non_exhastive`. | ||
See [repr-c-enum-variant-new](#repr-c-enum-variant-new). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reordering concern I mentioned above seems to pose an interesting question here as well: if the new variants of the #[repr(C)] #[non_exhaustive]
enum are added in the middle, they may change the (externally-observable) discriminants of existing variants. Might that be a major change?
Adding new variants after all existing variants seems safe, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think whether or not changing enum discriminant values is specific to the situation. I see that as a similar situation to whether or not changing a const
is a breaking change. I think in most cases the intent is to have some abstract representation of some value, but the exact value of the const (or enum discriminant) doesn't matter. https://internals.rust-lang.org/t/should-we-be-more-strict-with-const-and-semver/14302 contains more discussion on this. I'm not sure what the rule should be for that, but I added it to #8736 to track it.
* Removing `repr(C)` from a struct, union, or enum. | ||
See [repr-c-remove](#repr-c-remove). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there broad consensus on repr
being always public, or can it sometimes be private?
A few folks involved with the Rust project recently mentioned that in their view, it's possible for repr(C)
and other repr
attributes to be public, and that a removal of repr(C)
is not necessarily a semver-major change.
This is the PR in question where that discussion was taking place: rust-lang/rust#90435
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I misunderstood something, we're good.
* Adding `repr(align)` to a struct, union, or enum. | ||
See [repr-align-add](#repr-align-add). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is true, but given how rare and suboptimal repr(packed)
is, I suspect in practice some crates might not treat this as a breaking change.
Also, it seems like in theory we could remove the incompatibility between the two, though I doubt that's a priority.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, there's a TODO comment in that section about how unlikely that is to be a problem. I'm not sure how best to handle cases like that. There are more shades than I expected in the spectrum from "definitely compatible" to "definitely not compatible", and it is hard to know how much of a stickler to be.
I think this is something to revisit and I'm going to leave the TODO in there for now.
* Changing the order of public fields of a `repr(C)` type. | ||
See [repr-c-shuffle](#repr-c-shuffle). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like, as described, this is only a breaking change if the type has no private fields? Or should we consider the offsets of public fields to be stable even if there are private fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, the disclaimer at the end of this list saying "In some cases, types with a repr
attribute may not have an alignment, layout, or size that is well-defined." covers this well enough. (Might be worth mentioning that in the specific section, but not blocking on this.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rearranged the text to put the caveat higher. It can be challenging to be precise, but keep things concise enough to read without repeating all the qualifications.
r=me with the (very minor) wording nits fixed. |
Co-authored-by: the8472 <[email protected]>
Thanks for the review! |
☀️ Test successful - checks-actions |
Update cargo 15 commits in 7c3904d6c3ed54e8a413023519b55a536ad44d5b..80eca0e58fb2ff52c1e94fc191b55b37ed73e0e4 2023-08-14 20:11:43 +0000 to 2023-08-19 00:52:06 +0000 - chore: Downgrade serde below the binary blob (rust-lang/cargo#12528) - Improve error message for when no credential providers are available (rust-lang/cargo#12526) - Fix typo: "use" -> "used" (rust-lang/cargo#12522) - Document layout SemVer compatibility. (rust-lang/cargo#12169) - Make cargo-credential-gnome-secret built-in as cargo:libsecret (rust-lang/cargo#12521) - login: allow passing additional args to provider (rust-lang/cargo#12499) - cargo-credential-gnome-secret: dynamically load libsecret (rust-lang/cargo#12518) - credential-providers: make 1password no longer built-in (rust-lang/cargo#12507) - Print environment variables for `cargo run` in extra verbose mode (rust-lang/cargo#12498) - chore(cargo-util): bump version to 0.2.6 (rust-lang/cargo#12517) - credential: rename cargo:basic to cargo:token-from-stdout (rust-lang/cargo#12512) - fix(xtask-bump-check): query by package name to detect changes (rust-lang/cargo#12513) - ci: use pull request head commit whenever possible (rust-lang/cargo#12508) - Update hermit-abi (rust-lang/cargo#12504) - Crate checksum lookup query should match on semver build metadata (rust-lang/cargo#11447) r? ghost
Update cargo 15 commits in 7c3904d6c3ed54e8a413023519b55a536ad44d5b..80eca0e58fb2ff52c1e94fc191b55b37ed73e0e4 2023-08-14 20:11:43 +0000 to 2023-08-19 00:52:06 +0000 - chore: Downgrade serde below the binary blob (rust-lang/cargo#12528) - Improve error message for when no credential providers are available (rust-lang/cargo#12526) - Fix typo: "use" -> "used" (rust-lang/cargo#12522) - Document layout SemVer compatibility. (rust-lang/cargo#12169) - Make cargo-credential-gnome-secret built-in as cargo:libsecret (rust-lang/cargo#12521) - login: allow passing additional args to provider (rust-lang/cargo#12499) - cargo-credential-gnome-secret: dynamically load libsecret (rust-lang/cargo#12518) - credential-providers: make 1password no longer built-in (rust-lang/cargo#12507) - Print environment variables for `cargo run` in extra verbose mode (rust-lang/cargo#12498) - chore(cargo-util): bump version to 0.2.6 (rust-lang/cargo#12517) - credential: rename cargo:basic to cargo:token-from-stdout (rust-lang/cargo#12512) - fix(xtask-bump-check): query by package name to detect changes (rust-lang/cargo#12513) - ci: use pull request head commit whenever possible (rust-lang/cargo#12508) - Update hermit-abi (rust-lang/cargo#12504) - Crate checksum lookup query should match on semver build metadata (rust-lang/cargo#11447) r? ghost
This adds some documentation about whether or not alignment, layout, or size changes are SemVer-compatible.