Skip to content
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

Add semver guidelines for changing the repr of structs/enums to ref… #10276

Closed

Conversation

Nilstrieb
Copy link
Member

@Nilstrieb Nilstrieb commented Jan 9, 2022

The current semver guidelines do not mention repr at all. Changing the repr of a struct/enum will break code, especially unsafe code relying on layout. These breaking changes will rarely manifest themselves as compiler errors, which makes them all the more dangerous.

The new guidelines specify removing a well-defined repr from a struct or enum to be a breaking change.

This is more of a suggestion on how it could be done, and I'm very open for improvements for the wording and other details.

Another thing I'm not sure about is how this interacts with #[non_exhaustive], and therefore don't mention it at all.

There are also some other breaking changes that are related to this, like reordering the fields of a #[repr(C)] struct, and it's open for discussion whether these should be added as well.

discussion on zulip

@rust-highfive
Copy link

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @alexcrichton (or someone else) soon.

Please see the contribution instructions for more information.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 9, 2022
@Nilstrieb
Copy link
Member Author

I'm having a problem with the struct change, since it isn't a compiler error which causes CI to fail.

I could just do a dirty size assertion which would fix it, but that's a bad example imo

const _: () = assert_foo_size(); // Error: evaluation of constant value failed
const fn assert_foo_size() {
    let size = std::mem::size_of::<Foo>();
    if size != 6 {
        panic!("Foo has changed size!");
    }
}

Is there a flag to tell CI that it doesn't matter that this isn't a compiler error? Or do you know a better example that causes a compiler error?

@weihanglo
Copy link
Member

weihanglo commented Jan 11, 2022

I can’t find any other flag in the current implementation.

let output = cmd.output()?;
let stderr = std::str::from_utf8(&output.stderr).unwrap();
match (output.status.success(), expect_success) {

Maybe adapt semver-check to run the output executable, though that requires some efforts.
Or we can wait for feature(const_ptr_offset) stabilized 😆

@ehuss
Copy link
Contributor

ehuss commented Jan 11, 2022

I think it would be useful to extend semver-check to be able to have a "run-fail" mode. I think it shouldn't be too difficult to add. I would mark the example's language tag with something like

```rust,ignore,run-fail
# example here
```

And then check for that flag, and just run the code. From your example, I think that should be relatively safe.

Another possibility is to go with your static assert, but use rustdoc's hidden-line feature (using # as a prefix will hide those lines of code). However, I think semver-check would need to be adapted to strip the # prefix. I would lean towards adding the run-fail mode, though, as I think that could be useful in the future.

bors added a commit that referenced this pull request Jan 13, 2022
Add `run-fail` to semver-check for docs

I encountered this missing feature in #10276 and therefore added it here in this separate PR.

If the breaking change does not involve a compilation error but a change in runtime behaviour, you can add `run-fail` to the codeblock. The "before" code must return exit code 0, and the "after" code must be nonzero (like a panic).

Example case that I tested (ignore the trailing dot, it's for github markdown to not hate me)

```
```rust,ignore,run-fail
// MAJOR CHANGE

///////////////////////////////////////////////////////////
// Before
pub fn foo() {}
///////////////////////////////////////////////////////////
// After
pub fn foo() {
    panic!("hey!");
}
///////////////////////////////////////////////////////////
// Example usage that will break.
fn main() {
    updated_crate::foo();
}
```.
```
…erence

The current semver guidelines do not mention `repr` at all. Changing the `repr` of a struct/enum will break code, especially unsafe code relying on layout. These breaking changes will rarely manifest themselves as compiler errors, which makes them all the more dangerous.

The new guidelines mention removing a well-defined repr from a struct or enum to be a breaking change.
@Nilstrieb
Copy link
Member Author

That took some git magic, but let's see whether this passes now 😅

@joshtriplett
Copy link
Member

In general, I think there are a number of other possible ways that repr can be a breaking change.

I think it'd be good to generalize:

  • It is possible for another crate to depend on the size or alignment or memory layout of one of your structs/unions/enums.
  • Therefore, it is possible that a change to the size or alignment or memory layout of one of your structs/unions/enums may be a breaking change.
  • Whether such a change is a breaking change in practice depends on what crates actually depend on, and to a much lesser extent what they should be able to depend on. You can potentially mitigate a potential breaking change with documentation saying what is and isn't part of your stable interface...assuming people read your documentation and follow it.
  • Here are some (but not by any means all) ways that repr can affect the size or alignment or memory layout of your structs/unions/enums...

@Nilstrieb
Copy link
Member Author

While I like there being hard rules, I agree with you that it is a bit more nuanced with repr. Changing layout is more dangerous than other "classic" breaking changes, because it doesn't actually cause compiler errors in most cases.
But people relying on layout is also generally quite rare I'd guess, and is only supposed to happen with #[repr(C)] and #[repr(transparent)]. If people rely on the layout of your #[repr(Rust)] types, something very fishy is going on.

But it is really hard to come up with hard rules, and these general guidelines sound nice, but I'd still like to direct towards telling that only changing from well-defined reprs should be breaking.

let foo_ptr = &foo as *const Foo as *const u8;

// this is now unsound because of the change
// SAFETY: Foo is repr(C), so we are guaranteed that there will be `3` at this offset (u8, 8 pad, u16)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is what was meant?

Suggested change
// SAFETY: Foo is repr(C), so we are guaranteed that there will be `3` at this offset (u8, 8 pad, u16)
// SAFETY: Foo is repr(C), so we are guaranteed that there will be `5` at this offset (u8, 8 pad, u16)

Comment on lines +281 to +282
When a struct has a well-defined repr (`transparent`, `C`) and only public fields, this will break unsafe code relying on
that repr.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to try to reword this for clarity.

Suggested change
When a struct has a well-defined repr (`transparent`, `C`) and only public fields, this will break unsafe code relying on
that repr.
When a struct has a well-defined repr (`transparent`, `C`) and only public fields, changing the repr will break unsafe code relying on the original repr.

<a id="struct-change-repr-when-public"></a>
### Major: change a from well-defined repr to another when all fields are public

When a struct has a well-defined repr (`transparent`, `C`) and only public fields, this will break unsafe code relying on
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, is this qualified on "only public fields" because changes to private fields can potentially change the alignment, and that's explicitly hidden from the user?

@ehuss
Copy link
Contributor

ehuss commented Jan 27, 2022

I'm wondering if this can be generalized to a single "Major" change that is "Changing the layout of a well-defined type" or something like that.

Instead of trying to have separate sections for every repr modification that can break,
the single section can then enumerate the different ways changes to the repr attribute can cause problems.

For example, list out what kinds of types have well-defined layouts:

  • A struct with all public fields with #[repr(C)] or #[repr(transparent)].
  • A union with all public fields with #[repr(C)].
  • An enum with #[repr(C)]or#[repr(transparent)]`.
  • An enum with a primitive representation.

And say any change to the repr is a breaking change for those.

And also mention: Any change to align or packed that changes the alignment or layout.

(Note: I haven't thought about the specifics of what I wrote above, as I just quickly wrote them down, they may need adjustment.)

@alexcrichton alexcrichton removed their assignment Mar 8, 2022
@ehuss ehuss added S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 1, 2022
@ehuss ehuss self-assigned this Nov 15, 2022
@bors
Copy link
Collaborator

bors commented Jul 12, 2023

☔ The latest upstream changes (presumably #12339) made this pull request unmergeable. Please resolve the merge conflicts.

@ehuss
Copy link
Contributor

ehuss commented Aug 18, 2023

I'm going to close as repr and layout guidelines are now documented via #12169. Thank you for getting this started, though!

@ehuss ehuss closed this Aug 18, 2023
@Nilstrieb Nilstrieb deleted the add-repr-semver-guideline branch August 18, 2023 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants