Saturating is repr(transparent), same way as Wrapping, but it's not documented yet.
A type being #[repr(transparent)]
is part of its public API - that's why it shows up in the docs.
The docs aren't perfect about this:
... but in this case, Saturating
is fully public, so I suppose that's fine.
It's not always meant to be. Rust doesn't have a way to explicitly mark the repr
as an implementation detail or something downstream can rely on. Whether it's shown in the docs or not is a heuristic.
So when it's meant to be, the docs should also be explicit about it.
It use to always show up in the docs. Now the field has to be public. Probably it will also someday have to be non-hidden. These are all just tuning the heuristic IMO.
The main question everyone seems to miss here is:
Is Saturating<T>
guaranteed to have the same layout and ABI as T?
The type is annotated with "repr(transparent)", so it should have the same layout and ABI (unless some compiler magic happens). I proposed to document this here, but special care is needed when introducing new quarantees, so I'm asking here.
Discussion here can't make that guarantee; you need agreement from the libs team.
True, but a discussion here may attract people we need
the proper way to get official t-libs input is with an ACP
I don't understand. If #[repr(transparent)]
isn't a guarantee, then what it is for?
Removing #[repr(transparent)]
of a struct with a public field is a breaking change, so the stdlib already guarantees it won't be removed. And #[repr(transparent)]
ensures that the struct has the same layout as the containing field. And the field is already public. How can this possibly change?
If the intention was to not guarantee that Saturating<T>
and T
didn't have the same layout, perhaps the type shouldn't be marked #[repr(transparent)]
then? But this ship already sailed.
repr(transparent)
could just be there so the type itself could use it. It isn't necessarily public API. Similar to how repr(C)
doesn't mean you're dedicating the type to always have the same field order.
Rust currently doesn't have this feature (private type layouts). Perhaps it should! It would be nice to have it, something like #[private(repr(transparent))]
.
The status quo is that everything in the public API is something that downstream users might rely upon. Even if #[repr(transparent)]
were meant for internal use only, removing it would still a breaking change, just like removing a public field is a breaking change.
To refine this slightly, it's considered good practice to write a note in the type documentation when rustdoc shows a misleading #[repr]
attribute and you don't intend to provide any stable guarantees.
I have a type like #[repr(C)] struct { one: i32, pub two: i32 }
which showed up[1] in rustdoc as misleading #[repr(C)] struct { pub two: i32, /* private fields */ }
. You can bet that got a loud disclaimer not to rely on that actively misleading presented documentation.
Just to note, a warning was somewhat recently added for 1ZST in #[repr(transparent)]
where the 1ZST is not "fully publicly" known to be 1ZST. So there certainly is precedent that any nonpublic fields (or #[non_exhaustive]
) means the layout specifics are not considered stable API surface. (Although this doesn't apply to Saturating
, of course.)
It doesn't matter that you can write fully qualified safe code that asserts the type size_of
, align_of
, and field offset_of!
at build time; layout is not a part of stable API surface except in limited special circumstances — generally understood to be a stable #[repr]
and all public fields.
IIRC, the consensus was that #[repr(transparent)]
with the transparently wrapped field being public does in fact confer a stable API guarantee.
Explicitly stating this guarantee in the type documentation doesn't hurt, but it isn't necessary. Most types which call out transparent compatibility don't provide public field access, and thus must state it to tell downstream what they transparently act like.
It's been multiple years since I last generated docs for that library, so it might've changed since. ↩︎
All fields of Saturating
are indeed public.. and yes I was talking about structs with public fields; if the struct is #[repr(transparent)]
but wraps a private field, then I suppose you are allowed to change the type of this field (since it's private), and in this case the transparent repr wouldn't imply a stability guarantee.
Oh thank you.
IIRC, the consensus was that
#[repr(transparent)]
with the transparently wrapped field being public does in fact confer a stable API guarantee.Explicitly stating this guarantee in the type documentation doesn't hurt, but it isn't necessary. Most types which call out transparent compatibility don't provide public field access, and thus must state it to tell downstream what they transparently act like.
Please give a citation of this consensus, how guaranteed it is, and it's perceived scope, e.g. "this case only", "within std
generally".
Asking for this because
-
Your statement sounds like a general rule, but that's not something the teams should try to impose on the ecosystem
-
Casual consensus without guarantees can be negated later, and thus shouldn't be relied upon by others, and shouldn't be presented as something that can be relied upon either
Please give a citation of this consensus
I think it's likely buried in the discussion around the docs showing repr attributes too eagerly, and I wasn't able to find a citation, unfortunately.
But I do recall the reasoning being roughly
- Semver stability applies to the documented API
- rustdoc has always documented the presence of
#[repr]
, making it a part of the documented API - We should teach rustdoc to not document
#[repr]
when it doesn't confer any usable guarantees
and not much more than that. And the consensus was as I recall that this stability was already in effect guaranteed, and didn't need a separate guarantee.
As a further point of justification, #[repr]
was an attribute when every attribute (except lint attributes) was part of the interface of the decorated item, and the ability to have attributes that didn't have public visible effects came later. In fact #[repr]
can directly change field access semantics (much to the chagrin of many today), so obviously it's meaningful to API.
Also, the likelihood of a public type being #[repr(transparent)]
for private library usage and that capability not being somehow implied by the public API (plus an assumption of not wantonly leaking things) is very low. Possible, perhaps, but quite unlikely, made further so by having the field be public.
Also, the likelihood of a public type being
#[repr(transparent)]
for private library usage and that capability not being somehow implied by the public API (plus an assumption of not wantonly leaking things) is very low. Possible, perhaps, but quite unlikely, made further so by having the field be public.
What about a public but doc(hidden) field? (For macro purposes presumably.)
I think it's likely buried in the discussion around the docs showing repr attributes too eagerly, and I wasn't able to find a citation, unfortunately.
That means that there is no actual guarantee. There is only some implicit assumption perceived to be common. That's not a good precedent; it encourages people to assume a guarantee library maintainers may not intend; that in turn encourages Hyrum's law induced stagnation or anger when things get changed.
There is no FCP; some future libs team may in the future choose to remove some repr
(even if it happens to be shown by rustdoc
). It doesn't look like there was actual agreement of what is reasonable among the current teams to me. There is also no ecosystem standard; a library may choose to remove some repr
without releasing a new major version and not agree that it was a breaking change, whether or not they explicitly documented that it could not be relied upon; whether rustdoc
showed it or not.
There's especially no ecosystem agreement based on what rustdoc
shows, since those fenceposts are still being actively moved. Clealy, the rustdoc
showing the repr
can't be a guarantee to downstream, or #90435 would have been a breaking change (removing guarantees made to downstream) and #128364 would be too. Now there's a proposal to change the disposition of rustdoc
to not show the repr
in more cases, but then tune it to show it in some of those cases later on, in which case upstream can't rely on the rustdoc
not showing the repr
to be a non-guarantee; rustdoc
may start showing the repr
without notice!
It is not reasonable to expect everyone in the ecosystem to make the same conclusions about whether the repr
is guaranteed or not based on some combination of pub
visibility, non_exhaustive
, hidden
, and whatever someone else thinks of tomorrow.
It is not reasonable to say that what rustdoc
shows or does not show corresponds to guarantees or non-guarantees when it it being actively being tuned to show less today, and may be tuned to show more tomorrow.
The only safe course of action as a library consumer is to look for an explicitly documented guarantee, ask for one if it's not present, and assume there is no guarantee if there is no documentation.
Explicitly stating this guarantee in the type documentation doesn't hurt, but it isn't necessary.
In particular I don't see how this can be true. I'm not seeing agreement on when this is a reasonable assumption among the team, much less the entire ecosystem.
In terms of the OP in particularly, you're making it sound like libs team has guaranteed to something which, as far as I can tell, they have not. You cannot make that guarantee for them. I feel that is neither appropriate nor responsible to imply that the guarantee exists.
Especially as someone in a position some may view as authoritative.
Especially as someone in a position some may view as authoritative
I at least try to put disclaimers on and hedge against things related to opsem that I'm not actively checking citations for. (I still feel like I lucked into my position moreso than anyone else did.) I suppose I should be taking that same level of care for lib guarantees also.
Here I legitimately believed the promise was stronger than it seems to be in reality. I still hold that both pub fields and stable repr is a guarantee (otherwise every *-sys
style crate needs to spell out the implicit fact that their types are guaranteed to match ABI with the FFI ABI) in practice, but acquiesce that it seems that it doesn't have a formalized guarantee documented.
The other evidence for stability would be discussion on making ref-casting transparent types into a safe operation (given field visibility). It was dismissed as being incorrect semantics for the purposed syntax, but nobody commented on whether the ability to do so manually was confused a stable capability or not.
But that's still just circumstantial evidence.
If you don't want it to be part of the public API, can't you just spell it like this?
#[cfg_attr(not(doc), repr(transparent))]
Technically no, not in all cases, the repr(transparent)
could be relied upon in const
-context to fully type-check the crate in order to generate its docs.