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

Opaque concrete constants #39

Open
3 tasks
lcnr opened this issue Mar 23, 2022 · 0 comments
Open
3 tasks

Opaque concrete constants #39

lcnr opened this issue Mar 23, 2022 · 0 comments
Labels
A-unification Unifying constants in the type system C-design-docs Category: This is part of our design documentation K-behavior Document Kind: regarding user visible behavior P-optional Priority: not strictly required S-active

Comments

@lcnr
Copy link
Contributor

lcnr commented Mar 23, 2022

What is this

This is a design document for const generics. Any discussions about its content should be on zulip. The conclusions of these discussions should then be edited back into this issue. Please do not post any comments directly in this issue.

Content

adapted from a summary by @PoignardAzur.

Quite a lot of functions might not want their exact output to be part of their stability guarantees. A good example for this is mem::size_of::<T>().

With const generics, changing the return value of such functions can however end up being a breaking change.

// The fields of `MyIndex` are private, so it should not be a breaking
// change to change it to a `u64`.
struct MyIndex(u32);

// This is currently allowed but would break if the size of `MyIndex` changes.
//
// If we keep constants which use `size_of` opaque, this would not compile even
// though the sizes match up, removing a stability hazard.
let _: [u8; 4] = [0; size_of::<MyIndex>()];

While this is already an issue today, it may get even worse with the addition of complex generic constants, e.g:

trait Trait<const N: usize> {
    type Assoc;

    fn get_zero() -> Self::Assoc;
}

impl<const N: usize> Trait<N> for [u8; N] where N <= 4 {
    type Assoc = u32;

    fn get_zero() -> u32 { 0 }
}

impl<const N: usize> Trait<N> for [u8; N] where N > 4 {
    type Assoc = u64;

    fn get_zero() -> u64 { 0 }
}

Current stability guidelines

Looking at the SemVer Compatibility doc this is somewhat under-documented.

  • Changing public fields is explicitly described as a major change. Changing private fields (in a struct which already has private fields) isn't mentionned, and is therefore implied not to be a breaking change that requires a major or minor semver bump.
  • Changing the value of constants or the code of constant functions isn't mentioned at all.

Should we worry about this?

Currently any breakage tends to be fairly explicit, as it mostly happens by unifying an array, where the length is the result of a function call, with some array of a concrete length. This means that places where breakage may occur tend to be fairly obvious.

This may change in the future however. By using a bound like N > 4 for trait implementations it will be less clear whether one currently depends on the concrete return value of such a function. It will also make it less obvious what actually went wrong, making the resulting breakage more difficult to figure out.

And in any case, the current situation isn't ideal. Stability guarantees should be better documented, and enforced by machine rules instead of doc comments.

How could we remedy this

The language could have an annotation to mark that a constants value isn't part of the stability guarantee, and therefore shouldn't be relied on.

This annotation would have no effect within a crate, but would prevent downstream crates from unifying the constant with its value, among other things.

For instance:

// crate_a
#[opaque]
const A: i32 = 42;
let array: [i32; 42] = [0; A]; // no problem

// crate_b
let array: [i32; 42] = [0; A]; // error!

Opaqueness would be infectious:

#[opaque]
const X: i32 = 0;

#[opaque]
const Y: i32 = X; // OK

const Z: i32 = X; // ERROR!

Const functions could be declared as opaque as well, so that their output isn't a stability guarantee:

#[opaque]
const fn count_unicode_grapheme_clusters(text: &str) -> usize {
    // ...
}

Arguably, some developers may even want opaqueness to be the default for functions. It's unclear how common opaque const functions would be. On one hand, library authors don't want to bump semver whenever they change internal code.

On the other hand, there are functions where it's absolutely clear the output is never expected to change, like Vec::size or iter::find, even if their internal algorithm changes.

The most notable function is mem::size_of. Its output is explicitly not part of Rusts stability guarantees but people still want to rely on the exact size of their structs. So while we won't be able to completely forbid that, we should consider to lint in these cases instead. @PoignardAzur discusses this in more detail.

Next steps

While this is strongly related to const generics its actual design and implementation are mostly independent of it. I would expect us to take the following steps for this:

  • open a lang team MCP to add the notion of opaque constants and to clarify the stability guarantees of constants and functions.
  • implement opaque constants in the compiler
  • add them to relevant methods and constants in the standard library, doing crater runs and probably linting in case one relies on the value of an opaque constant.
@lcnr lcnr added C-design-docs Category: This is part of our design documentation K-behavior Document Kind: regarding user visible behavior P-optional Priority: not strictly required S-active A-unification Unifying constants in the type system labels Mar 23, 2022
@rust-lang rust-lang locked and limited conversation to collaborators Mar 23, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A-unification Unifying constants in the type system C-design-docs Category: This is part of our design documentation K-behavior Document Kind: regarding user visible behavior P-optional Priority: not strictly required S-active
Projects
None yet
Development

No branches or pull requests

1 participant