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

Discussion/Pre-RFC/ACP: Unify privately constructed ("protected") ZSTs in std #3640

Closed
LunarLambda opened this issue May 20, 2024 · 0 comments

Comments

@LunarLambda
Copy link

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:

pub struct Tuple(());

The second is a unit struct with the #[non_exhaustive] attribute:

#[non_exhaustive]
pub struct Unit;

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:

  1. [Style] Which of the two forms is considered more idiomatic/prefered today? Should this be documented somewhere?
  2. [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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant