Skip to content

Tracking issue for future-incompatibility lint const_evaluatable_unchecked #76200

Open

Description

@lcnr

This is the summary issue for the CONST_EVALUATABLE_UNCHECKED future-compatibility warning. The goal of this page is to describe why this change was made and how you can fix code that is affected by it. It also provides a place to ask questions
or register a complaint if you feel the change should not be made. For more information on the policy around future-compatibility warnings, see our breaking change policy guidelines.

What is the warning for?

In version 1.43 we accidentally allowed some uses of generic parameters in repeat expressions. This has a few problems which are outlined in the following document for now.

When will this warning become a hard error?

Most probably after const well-formedness bounds get stabilized, which is currently not yet implemented. There might be a subset of cases here which we can allow without needing any changes, but these require further careful consideration.

How to fix this?

As we only allowed generic constants where the actual type did not matter, it should always be possible to replace the generic parameters with dummy types. For example [0; std::mem::size_of::<*mut T>()] can be changed to [0; std::mem::size_of::<*mut ()>()].

Please report your case here so we may take it into consideration when deciding on how to proceed.

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    A-const-genericsArea: const generics (parameters and arguments)
    on Sep 1, 2020
  2. added
    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)
    T-langRelevant to the language team
    and removed
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Sep 1, 2020
  3. m-ou-se commented on Sep 12, 2020

    @m-ou-se
    Member

    Please report your case here

    Merging #74532 just now ran into this problem. I worked around it by using () as dummy type:

    -        let [] = [(); align_of::<Self>() - align_of::<*mut T>()];
    +        let [] = [(); align_of::<AtomicPtr<()>>() - align_of::<*mut ()>()];

    Here, not only *mut T was a problem, but Self (which is AtomicPtr<T>) was a problem as well.

  4. danielhenrymantilla commented on Feb 16, 2021

    @danielhenrymantilla
    Contributor

    Following from https://users.rust-lang.org/t/cant-understand-warning-when-using-const-evaluatable-checked/55630/6?u=yandros, the following snippet causes three warnings associated with the lint of this tracking issue, and a hard error:

    const _10: usize = 10;
    
    fn write_struct<T> ()
    where
        can_be_evaluated!(_10 - 0):,
    {
        foo::<T, 10>(); // Warning
        foo::<T, { _10 }>(); // Error (same for `{ 10 }`)
        WithConst::<10>::foo::<T>(); // Warning
        WithConst::<{ _10 }>::foo::<T>(); // Warning
        
        WithConst::<10>::write_struct::<T>(); // OK
        WithConst::<{ _10 }>::write_struct::<T>(); // OK
    }

    where foo in both cases features, for some generic constant N:

    • some non-generic type with an evaluatable constraint / that depends on a(n assumed) fallible computation of N (N - 0 in the examples; note that I am not talking about N - 0 being infallible, but rather, of a computation that does not fail for a concrete value of N (10 in the example));

    • an unrelated (to the above type) type parameter T.

    In the case of WithConst, I am using a helper type to introduce N (and the evaluatable bound!) clearly before the generic type T is ever introduced, to ensure the evaluatable bound cannot possibly depend on T.

    As you can see, a caller that:

    • forwards the generic type parameter,

    • but using a concrete constant parameter (one that does verify the evaluatable bound),

    now triggers either this warning, or some related error when the constant is braced (which is sadly required when using a non-literal constant; even when the function features an evaluatable bound on said non-literal constant).

    Finally, the last two calls in this example are interesting: by moving the logic of the function within the generic-and-evaluatable-bounded impl block for WithConst, we can later introduce that <T> type parameter without issues, and we can then call that helper function from outside with a fixed constant and it doesn't trigger a warning whatsoever.


    This leads to two questions:

    • is this warning behavior really intended to affect these things?

    • And what about the workaround: is that intended too?

  5. lcnr commented on Feb 26, 2021

    @lcnr
    ContributorAuthor

    From what I can tell this is not expected, is_const_evaluatable seems to be incorrect here if feature(const_evaluatable_checked) is active.

    Afaict all of the examples you've given should work without warnings or errors.

  6. is8ac commented on Sep 17, 2021

    @is8ac

    Please report your case here

    #![feature(generic_const_exprs)]
    
    fn foo<T: Default + Copy, const L: usize>(_: [T; L]) -> [T; L + 1] {
        [T::default(); L + 1]
    }
    
    fn bar(x: [u8; 2]) -> [u8; 3] {
        foo::<u8, 2>(x)
    }
    
    fn baz<T: Default + Copy>(x: [T; 2]) -> [T; 3] {
        foo::<T, 2>(x)
    }
    
    fn main() {}
    

    In 1.57.0-nightly (e4828d5b7 2021-09-16) baz produces warning: cannot use constants which depend on generic parameters in types. bar does not.

  7. tspiteri commented on Feb 23, 2022

    @tspiteri
    Contributor

    While checking whether the fixed crate can be ported from typenum to nightly with generic_const_exprs, I hit this warning. I am not sure whether it's a false positive or whether the behavior I want is meant to be disallowed, but I think it should be allowed when enabling generic_const_exprs.

    I've reduced the code though I left some stuff to show why it would be useful.

    #![feature(generic_const_exprs)]
    
    pub struct If<const CONDITION: bool>;
    pub trait True {}
    impl True for If<true> {}
    
    pub struct FixedI8<const FRAC: u32> {
        pub bits: i8,
    }
    
    impl<const FRAC_LHS: u32, const FRAC_RHS: u32> PartialEq<FixedI8<FRAC_RHS>> for FixedI8<FRAC_LHS>
    where
        If<{ FRAC_RHS <= 8 }>: True,
    {
        fn eq(&self, _rhs: &FixedI8<FRAC_RHS>) -> bool {
            unimplemented!()
        }
    }
    
    impl<const FRAC: u32> PartialEq<i8> for FixedI8<FRAC> {
        fn eq(&self, rhs: &i8) -> bool {
            let rhs_as_fixed = FixedI8::<0> { bits: *rhs };
            PartialEq::eq(self, &rhs_as_fixed)
        }
    }
  8. lcnr commented on Feb 23, 2022

    @lcnr
    ContributorAuthor

    @tspiteri this is a bug which only happens with feature(generic_const_exprs), opened #94293 to track that

  9. peter-lyons-kehl commented on May 15, 2022

    @peter-lyons-kehl
    Contributor

    I admit not understanding this (and reasoning behind it) fully. But I have use case for an associative type with a bound that depends on its owner (trait)'s const generic parameter. Any alternatives/workarounds, please?

    Short example failing no Nightly (thanks to ilyvion#9946 for narrowing it down): https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=c133c09b2fa155bb3f4b9ee45e6c011e.

    My use case: https://github.com/ranging-rs/slicing-rs/blob/main/src/slices/mod.rs#L30=. I know it's a semantic/voluntary-like constraint only, but I believe it deserves its place. (Feel free to follow up at ranging-rs/slicing-rs#1.)

  10. 11 remaining items

  11. added
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    on Sep 14, 2024
  12. Manishearth commented on Oct 11, 2024

    @Manishearth
    Member

    ICU4X hits this here:

    https://github.com/unicode-org/icu4x/blob/fcef14a31dbe5e2d0ee5f2a489872df373c2d0de/utils/zerotrie/src/options.rs#L135-L138

    We use Self::FLAGS in a bunch of match statements, so it must be a constant.

    This const is not dependent on the generic parameter.

  13. added
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    and removed
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    on Dec 21, 2024
  14. KizzyCode commented on Feb 17, 2025

    @KizzyCode

    Correct; using them as values is fine, but using them in types (like in an array size) is problematic. There's a big difference between these two ways of using something. That should hopefully be less surprising if you consider that runtime values are entirely forbidden as array sizes! Just because consts are allowed in both places, doesn't mean that suddenly these fundamental differences disappear. Consts are allowed in both places but they behave quite differently.

    Now, this is all preliminary, and I don't know the long-term plans for generic consts. But there is an underlying consistency here. The confusing stems from conflating runtime expressions and compile-time expressions even though those are completely different beasts.

    I think what bugs me here is that it's obvious for my brain that this value is "fully constant". FIFO_SIZE should always be 0xFF, as it does not depend on the generic parameter – if I don't miss anything, there is no way this code could ever compile or specialize to something else then 0xFF in current Rust.

    And if that assumption is true, IMO this should be treated as a special case that is "fully constant", and should not fail. Of course, this is from my developer-UX "consumer" perspective – I don't know how hard it is to make the compiler prove that case, or if it would block future language development 😅

    (Sorry for the late reply; I somehow accidentally unsubscribed here.)

  15. Ddystopia commented on Feb 28, 2025

    @Ddystopia
    Contributor

    I guess there is some regression, as it warned this piece of code (warning and code are redacted):

            let mut buf = [0u8; const { size_of::<::foo::bar::Baz>() }];
    warning: cannot use constants which depend on generic parameters in types
      --> <redacted>:38:29
       |
    38 |         let mut buf = [0u8; const { size_of::<::foo::bar::Baz>() }];
       |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
       = note: for more information, see issue #76200 <https://github.com/rust-lang/rust/issues/76200>
       = note: `#[warn(const_evaluatable_unchecked)]` on by default

    It clearly does not use any generics.

  16. BoxyUwU commented on Mar 6, 2025

    @BoxyUwU
    Member

    @Ddystopia inline consts desugar to an item that uses all in scope generic parameters, you should remove the const block there as it does not accomplish anything, if you need braces around an array length simply writing { ... } should work fine. no need for const { ... }

  17. MorganBennetDev commented on Apr 28, 2025

    @MorganBennetDev

    Ran into this warning and wanted to share my confusion with it. My confusion stems from the fact that this warning seems to be triggered inconsistently. Example of this

    pub struct Example<const CONSTANT: u32>;
    
    impl<const CONSTANT: u32> Example<CONSTANT> {
        const DOESNT_CHANGE: usize = 3;
        const ALSO_DOESNT_CHANGE: usize = Self::DOESNT_CHANGE;
        
        pub fn does_trigger_warning(&self, i: usize) -> usize {
            match i {
                Self::ALSO_DOESNT_CHANGE => 0,
                _ => 1
            }
        }
        
        pub fn doesnt_trigger_warning(&self, i: usize) -> usize {
            match i {
                Self::DOESNT_CHANGE => 0,
                _ => 1
            }
        }
        
        pub fn also_doesnt_trigger_warning(&self, i: usize) -> usize {
            if i == Self::ALSO_DOESNT_CHANGE {
                0
            } else {
                1
            }
        }
    }

    The other thing that I found confusing was that the warning shows up on the declaration of DOESNT_CHANGE and not the usage (which is what caused the warning to appear).

    I am using cargo 1.88.0-nightly (d811228b1 2025-04-15) and rustc 1.88.0-nightly (077cedc2a 2025-04-19).

  18. BoxyUwU commented on Apr 29, 2025

    @BoxyUwU
    Member

    @MorganBennetDev that seems like a bug to me. I don't expect us to make that an error when we fix the special casing of repeat expr counts. I split that out into #140447

  19. edwloef commented on Nov 3, 2025

    @edwloef
  20. Sycrosity commented on Dec 3, 2025

    @Sycrosity

    I'm a bit confused on whether the triggering of this warning is expected behaviour or not.

    Code, Errors and Questions

    ```rust struct Identifier(heapless::String);

    impl Identifier {
    const NAMESPACE: &'static str = "default";
    }

    impl Decode for Identifier {
    async fn decode(...) -> Result {

        ...
    
        let mut namespace_buf = [0u8; Self::NAMESPACE.len() + 1];
    
        ...
    }
    

    }

    
    Results in the error:
    
    

    warning: cannot use constants which depend on generic parameters in types
    --> src/identifier.rs:43:39
    |
    43 | ...f = [0u8; Self::NAMESPACE.len() + 1];
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #76200 #76200

    
    However, if I extract `Self::NAMESPACE.len()` into its own const, it results in no warning:
    
    ```rust
    
    ...
    impl<const N: usize> Identifier<N> {
        const NAMESPACE: &'static str = "default";
        const NAMESPACE_LEN = Self::NAMESPACE.len();
    }
    
    impl<const N: usize> Decode for Identifier<N> {
        async fn decode(...) -> Result<Self> {
            ...
            let mut namespace_buf = [0u8; Self::NAMESPACE_LEN + 1];
            ...
        }
    }
    

    This even works if const NAMESPACE_LEN: usize = Identifier::<N>::NAMESPACE.len().
    In both situations, NAMESPACE.len() does not depend on N, but the const's are impl's of a generic type.

    If this is true:

    By declaring it inside the impl block, your const implicitly becomes dependent on all generic parameters of the type, and that's what triggers the warning.

    then neither of these implementations should work, but from a DX POV they should both work.

    If I missing something here please do correct me.

    Using cargo 1.91.1 (ea2d97820 2025-10-10)

  21. lcnr commented on Dec 3, 2025

    @lcnr
    ContributorAuthor

    @Sycrosity can you provide a complete example where it does not lint?

  22. Sycrosity commented on Dec 3, 2025

    @Sycrosity

    @lcnr I think I accidentally used Identifier::<1>::NAMESPACE_LEN() rather than Self::NAMESPACE_LEN() which I believe is the intended behaviour (though as others have mentioned, feels expected). I've provided the two implementations below just in case.

    Code where the lint is triggered

    impl<const N: usize> Identifier<N> {
        const NAMESPACE: &'static str = "default";
        
    
        pub fn new<S: Into<String<N>>>(s: S) -> Self {
            Identifier(s.into())
        }
    }
    
    impl<const N: usize> Encode for Identifier<N> {
        async fn encode<W: embedded_io_async::Write>(
            &self,
            mut buffer: W,
        ) -> Result<(), EncodeError<W::Error>> {
    
            
            VarInt(Self::NAMESPACE.len() as i32 + self.0.len() as i32)
                .encode(&mut buffer)
                .await?;
            buffer.write(Self::NAMESPACE.as_bytes()).await?;
            buffer.write_u8(b':').await?;
            buffer.write(self.0.as_bytes()).await?;
            Ok(())
        }
    }
    
    impl<const N: usize> Decode for Identifier<N> {
        async fn decode<R: embedded_io_async::Read>(
            mut buffer: R,
        ) -> Result<Self, DecodeError<R::Error>> {
            let length = *VarInt::decode(&mut buffer).await?;
    
            if length < Self::NAMESPACE.len() as i32 + 1 {
                return Err(DecodeError::VarIntTooSmall(VarInt(0)));
            }
    
            if length as usize > Self::NAMESPACE.len() + 1 + N {
                return Err(DecodeError::VarIntTooBig);
            }
    
            // #[allow(const_evaluatable_unchecked)]
            let mut namespace_buf = [0u8; Self::NAMESPACE.len() + 1];
    
            buffer.read_exact(&mut namespace_buf).await?;
    
            let mut path_buf = Vec::<u8, N>::new();
    
            let path_length = length as usize - Self::NAMESPACE.len() - 1;
    
            //the error from this shouldn't be possible as we've already checked the
            // length.
            path_buf
                .resize_default(path_length).expect("length already checked");
    
            buffer.read_exact(&mut path_buf).await?;
    
            Ok(Self(
                String::from_utf8(path_buf).map_err(DecodeError::InvalidUtf8)?,
            ))
        }
    }

    Code which doesn't lint

    impl<const N: usize> Identifier<N> {
        const NAMESPACE: &'static str = "default";
        const NAMESPACE_LEN: usize = Self::NAMESPACE.len();
    
        pub fn new<S: Into<String<N>>>(s: S) -> Self {
            Identifier(s.into())
        }
    }
    
    impl<const N: usize> Encode for Identifier<N> {
        async fn encode<W: embedded_io_async::Write>(
            &self,
            mut buffer: W,
        ) -> Result<(), EncodeError<W::Error>> {
    
            
            VarInt(Self::NAMESPACE.len() as i32 + self.0.len() as i32)
                .encode(&mut buffer)
                .await?;
            buffer.write(Self::NAMESPACE.as_bytes()).await?;
            buffer.write_u8(b':').await?;
            buffer.write(self.0.as_bytes()).await?;
            Ok(())
        }
    }
    
    impl<const N: usize> Decode for Identifier<N> {
        async fn decode<R: embedded_io_async::Read>(
            mut buffer: R,
        ) -> Result<Self, DecodeError<R::Error>> {
            let length = *VarInt::decode(&mut buffer).await?;
    
            if length < Self::NAMESPACE.len() as i32 + 1 {
                return Err(DecodeError::VarIntTooSmall(VarInt(0)));
            }
    
            if length as usize > Self::NAMESPACE.len() + 1 + N {
                return Err(DecodeError::VarIntTooBig);
            }
    
            // #[allow(const_evaluatable_unchecked)]
            let mut namespace_buf = [0u8; Identifier::<1>::NAMESPACE_LEN + 1];
    
            buffer.read_exact(&mut namespace_buf).await?;
    
            let mut path_buf = Vec::<u8, N>::new();
    
            let path_length = length as usize - Self::NAMESPACE.len() - 1;
    
            //the error from this shouldn't be possible as we've already checked the
            // length.
            path_buf
                .resize_default(path_length).expect("length already checked");
    
            buffer.read_exact(&mut path_buf).await?;
    
            Ok(Self(
                String::from_utf8(path_buf).map_err(DecodeError::InvalidUtf8)?,
            ))
        }
    }

  23. added
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    and removed
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Feb 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)A-const-genericsArea: const generics (parameters and arguments)A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.C-future-incompatibilityCategory: Future-incompatibility lintsC-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions