You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There are two primary ways that std uses to create ZSTs that can only be constructed by std, hereafter referred to as "protected ZSTs":
The first is a tuple struct with a single, private, typically () field:
pubstructTuple(());
The second is a unit struct with the #[non_exhaustive] attribute:
#[non_exhaustive]pubstructUnit;
This makes the constructor of Unit (i.e. writing Unit) have pub(crate) visibility.
In both cases, the only thing the user can do with the type, other than interact with its methods/trait implementations, is to destructure it specifically via Type { .. } syntax. Any other destructuring syntax is not possible.
This means that changing one form to the other or vice versa does not constitute a breaking change. In fact, this kind of change has already previously occured in std, as types that have been stable since before #[non_exhaustive] was stabilized in 1.40.0 were changed to be #[non_exhaustive] unit structs (see below).
Affected Types
I have gone through the entirety of std's currently (1.78.0) stable, public types and compiled which ones use which form, alongside their stable_since versions.
Tuple Structs (9)
std::array::TryFromSliceError (1.34.0)
std::char::CharTryFromError (1.34.0)
std::char::TryFromCharError (1.59.0)
std::ffi::c_str::FromBytesUntilNulError (1.69.0)
std::num::TryFromIntError (1.34.0)
std::os::windows::io::InvalidHandleError (1.63.0)
std::os::windows::io::NullHandleError (1.63.0)
std::path::StripPrefixError (1.7.0)
std::str::FromUtf16Error (1.0.0)
Non-exhaustive Unit Structs (5)
std::alloc::LayoutError (1.50.0)
std::io::Empty (1.0.0)
std::io::Sink (1.0.0)
std::str::ParseBoolError (1.0.0)
std::thread::AccessError (1.26.0)
Comparison
These are largely subjective arguments for and against each of the two variants, based on personal experience and talking to people in the community.
Tuple Structs
Pros:
It's the most immediately obvious way for the programmer to define a protected ZST.
There visibility of the field and thus of the type's constructor can be specified explicitly.
Cons:
In documentation it's not immediately obvious whether such a type holds meaningful data or not, as it simply shows Type(/* private fields */), same as for types that do carry data. Fixing this in rustdoc would leak internal details so is an obvious no-go, so the only alternative would be the author explicitly documenting it, or the user having to check the source code.
Type(()) is more bothersome to write, and can actually be confusing to newcomers encountering tuple structs and () for the first time.
It makes derived Debug output less clean.
Non-exhaustive Unit Structs
Pros:
It is obvious from both source code and documentation that the type is a ZST.
It is the most aesthetically clean option.
Cons:
The constructor is always and implicitly pub(crate), which may not always be desirable.
The behaviour and stability guarantees aren't obvious to people today. Documenting them explicitly in the reference would be a sufficient fix I think.
Questions
The questions I want to answer, possibly with the goal of an eventual RFC/ACP, are as follows:
[Style] Which of the two forms is considered more idiomatic/prefered today? Should this be documented somewhere?
[Libs] Should std commit to one of the two forms by default, and unify the current representation of the above types? (This would be the subject of a future RFC/ACP)
While I am a fan of non-exhaustive unit structs to create protected ZSTs, I can also see them being considered more of an anti-pattern due to their arguably unintuitive nature. In that case I would still like that to become documented in some way.
The text was updated successfully, but these errors were encountered:
Preamble
There are two primary ways that std uses to create ZSTs that can only be constructed by std, hereafter referred to as "protected ZSTs":
The first is a tuple struct with a single, private, typically () field:
The second is a unit struct with the
#[non_exhaustive]
attribute:This makes the constructor of
Unit
(i.e. writingUnit
) havepub(crate)
visibility.In both cases, the only thing the user can do with the type, other than interact with its methods/trait implementations, is to destructure it specifically via
Type { .. }
syntax. Any other destructuring syntax is not possible.This means that changing one form to the other or vice versa does not constitute a breaking change. In fact, this kind of change has already previously occured in std, as types that have been stable since before
#[non_exhaustive]
was stabilized in 1.40.0 were changed to be#[non_exhaustive]
unit structs (see below).Affected Types
I have gone through the entirety of std's currently (1.78.0) stable, public types and compiled which ones use which form, alongside their
stable_since
versions.Tuple Structs (9)
std::array::TryFromSliceError
(1.34.0)std::char::CharTryFromError
(1.34.0)std::char::TryFromCharError
(1.59.0)std::ffi::c_str::FromBytesUntilNulError
(1.69.0)std::num::TryFromIntError
(1.34.0)std::os::windows::io::InvalidHandleError
(1.63.0)std::os::windows::io::NullHandleError
(1.63.0)std::path::StripPrefixError
(1.7.0)std::str::FromUtf16Error
(1.0.0)Non-exhaustive Unit Structs (5)
std::alloc::LayoutError
(1.50.0)std::io::Empty
(1.0.0)std::io::Sink
(1.0.0)std::str::ParseBoolError
(1.0.0)std::thread::AccessError
(1.26.0)Comparison
These are largely subjective arguments for and against each of the two variants, based on personal experience and talking to people in the community.
Tuple Structs
Pros:
Cons:
Type(/* private fields */)
, same as for types that do carry data. Fixing this in rustdoc would leak internal details so is an obvious no-go, so the only alternative would be the author explicitly documenting it, or the user having to check the source code.Type(())
is more bothersome to write, and can actually be confusing to newcomers encountering tuple structs and()
for the first time.Non-exhaustive Unit Structs
Pros:
Cons:
pub(crate)
, which may not always be desirable.Questions
The questions I want to answer, possibly with the goal of an eventual RFC/ACP, are as follows:
While I am a fan of non-exhaustive unit structs to create protected ZSTs, I can also see them being considered more of an anti-pattern due to their arguably unintuitive nature. In that case I would still like that to become documented in some way.
The text was updated successfully, but these errors were encountered: