# Idea: diagnostics::fickle

**URL:** <https://internals.rust-lang.org/t/idea-diagnostics-fickle/18578>\
**Category:** language design\
**Created:** [March 26, 2023, 7:43pm UTC](https://internals.rust-lang.org/t/idea-diagnostics-fickle/18578 "2023-03-26T19:43:16Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [March 26, 2023, 7:43pm UTC](https://internals.rust-lang.org/t/idea-diagnostics-fickle/18578/1 "2023-03-26T19:43:16Z")

</div>

Quick concept. There's a number of constants (e.g. `UNICODE_VERSION`) where the value is constant for any specific library version and target/configuration combination, but changing one of those is allowed to change the value of the constant. As such, introducing these constants into the type system via const generics is a potential footgun.

It'd be nice if we could mark such constant items as "fickle" and have the compiler warn when using them in a context which isn't itself marked as fickle. This would be granular; it's an accepted usage if the function or `const` item definition it appears in is marked as fickle. Using a fickle const as a const generic would be marked with `#[allow]` to acknowledge the lint and that this use is deliberate and handles the potential variance. Use outside the type system (i.e. in non-`const` expressions) would of course remain non linted. (I'm unsure on whether inline `const` blocks should lint fickle consts.)

There should probably be some looseness around "universal" usage, so e.g. all the usage of `[u8; mem::size_of<T>]` doesn't suddenly lint everywhere, since that's probably nonproblematic usage. The "full smart" analysis would be the lack of any non-universally-quantified impls on the type, but that's unfortunately a very global question, and an open one at that, since crates can add new trait impls. (More precise but also significantly more complicated would be only linting once a bounded obligation is required. Also deliberately ignores post-mono filtering of the const, since that's, well, post-mono\[1\].)

This could be extended to traits where removing an impl shouldn't be considered breaking. Currently stable, I think that's only `Drop`, but IIRC a primary reason `Freeze` is still private (despite technically being publicly observable via const restrictions) is that whether a type is `Freeze` (contains any shared mutable state before indirection) isn't considered API-stable information, and documenting it could give the impression that it is. Having a clear marker that it's fickle mitigates that somewhat. I also vaguely recall some other potential `const fn` type introspection in the vein of `mem::needs_drop` that haven't been provided for similar concerns.

I suppose this technically falls under the general category of the portability lint group.

* * *

1. `const` panic triggered post-mono errors _might_ potentially be moved up earlier in the pipeline to "post instantiation" but pre codegen to eliminate build/optimization-dependence. Don't quote me on this. See recent discussion w.r.t. the stabilization of `inline_const` for the context.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [March 26, 2023, 11:15pm UTC](https://internals.rust-lang.org/t/idea-diagnostics-fickle/18578/2 "2023-03-26T23:15:35Z")

</div>

This reminds me of the same problem with array lengths that came up in

> <https://github.com/rust-lang/rfcs/pull/2545#issuecomment-661305546>
>
> An observation here: this feels like the \_value\_-space version of what \`impl Tra…it\` does in the \_type\_-space.
> 
> To flesh that out:
> \- It's long been the Rust rule that items have their types fully specified. I think just changing this one specific bit isn't the right holistic change. As a trivial example, why would it be allowed in \`const FOO\` but not \`const fn foo()\`?
> \- Looking at the goal here, it seems to be that the crate doesn't want to promise a specific size from a semver perspective -- if it did it could just put that one by copying it from the error -- but at the same time allow the compiler to know what the concrete value there is when compiling things. That sounds very much to me like the same thing we had with types, where we wanted to add a way for people to promise that they're returning some iterator of \`u32\`s, but not its concrete type. To push that a bit further, the proposed example of \`static BLORP\_NUMBERS: \[u32; \_\] = \[0, 8, 15\];\` can be roughly thought of as being \`static BLORP\_NUMBERS: impl Copy + AsRef\<\[u32\]\> = \[0, 8, 15\];\`: you can copy it and a contiguous (thus indexable) thing, but you can't actually see its length at compile time, only at runtime.
> \- So to me, rather than this proposal having the size be \_inferred\_ -- which would make changing the size a breaking change because one could assign it to something of non-inferred size -- this should have additional semantic rules. So then the example would mean something like \`static BLORP\_NUMBERS: \[u32; impl usize\] = \[0, 8, 15\];\`: we promise there's some \`usize\` there, but you can't take a dependency on \_which\_ one is there. (That syntax being a placeholder, of course, just there to ape \`impl Trait\`.)
> 
> (Note that I'm only talking about \`static\` and \`const\` here -- having something like this for \`let\` seems clearly good, and if I remember the discussions about things correctly I think it's going to happen as part of const generics.)

---

<div class="post-metadata">

**Author:** ![canndrew](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/canndrew/32/1676_2.png) [@canndrew](https://internals.rust-lang.org/u/canndrew)\
**Post date:** [March 27, 2023, 5:58am UTC](https://internals.rust-lang.org/t/idea-diagnostics-fickle/18578/3 "2023-03-27T05:58:40Z")

</div>

I've mentioned this elsewhere before but rust really needs to have two levels of `pub` - one which exports the definition of an item and one which merely exports its existence. For the sake of explanation, let's call these `pub` and `pub(weak)`.

For constants, the difference is whether the value of the constant is visible to at compile-time. eg.

```rust
mod stuff {
    pub const PUB_LEN: usize = 3;
    pub(weak) const WEAK_LEN: usize = 3;
}

use stuff::{PUB_LEN, WEAK_LEN};

// this type-checks
let foo: [u32; PUB_LEN] = [1, 2, 3];

// this does not
let bar: [u32; WEAK_LEN] = [1, 2, 3];
// error: mismatched types
// expected an array with a fixed size of WEAK_LEN elements, found one with 3 elements.
// note: stuff::WEAK_LEN equals 3 but is declared as pub(weak).

```

That is, constants that are declared `pub(weak)` won't normalize to their value when they're used outside their defining module. Constant expressions involving them will get stuck and stop normalizing once the next step of evaluation requires inspecting the value.

For const functions the exact same logic can apply - they won't normalize outside their defining module and so you can end up with the type-checker handling types like `[u32; my_const_fn(3)]`.

For types, `pub(weak)` gives you a way to export a type while hiding its implementation even if the type doesn't happen to be a struct. Right now if you want to export a type from your library - and that type happens to be implemented as an enum or type alias - then the only way to hide the implementation is to use a wrapper struct. `pub(use)` would allow you to just export the type directly. In fact I think it would also completely negate the need for having visibility qualifiers on struct fields, since most structs either export all or none of their fields and those that export none don't need to export the fact that they're a struct at all, whereas those that export some could usually be improved by factoring those private fields out into a separate opaque type.

For traits, `pub(weak)` would export the existence of a trait and its signature (ie. its generic params and super-traits) but wouldn't export the trait's definition (ie. its associated items). This makes it impossible for downstream users to implement the trait or access those associated items but they could still use the trait in trait bounds. ie. `pub(weak)` on a trait makes it a sealed trait, with the added restriction that if you want to have methods that users can call then you need to put them in an extension trait.

Idris has this btw. `public` exports the signature of a declaration but keeps its definition private during type-checking, whereas `public export` exports the definition too.

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jrose/32/9591_2.png) [@jrose](https://internals.rust-lang.org/u/jrose)\
**Post date:** [March 27, 2023, 4:17pm UTC](https://internals.rust-lang.org/t/idea-diagnostics-fickle/18578/4 "2023-03-27T16:17:08Z")

</div>

I’m not 100% sure these capabilities should all be grouped under the same name, but they would all be useful. I’ll note that you can use a `static` for your weak `const` and a wrapper function for your weak `const fn`, but that’s pretty verbose.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [June 25, 2023, 4:17pm UTC](https://internals.rust-lang.org/t/idea-diagnostics-fickle/18578/5 "2023-06-25T16:17:32Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
