Tracking issue for future-incompatibility lint `const_evaluatable_unchecked`
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.
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.
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 - 0in the examples; note that I am not talking aboutN - 0being infallible, but rather, of a computation that does not fail for a concrete value ofN(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?
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.
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.
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)
}
}
@tspiteri this is a bug which only happens with feature(generic_const_exprs), opened #94293 to track that
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 https://github.com/ranging-rs/slicing-rs/issues/1.)
@peter-kehl this is #94293 again, this lint is only relevant when not using feature(generic_const_exprs)
I don't really understand the rationality of this limitation, also, it's weird to need to read it on external link and not directly in github. I think this subject didn't get much attention.
Recent question where an user would want to use it How can I give a name to the constant size of an array which is a member of a struct?
Disclaimer: I'm neither a compiler engineer, nor a typesystem-language-lawyer – I'm sure, from a technical point of view, the rationale behind this is probably sound.
However, if I look at this from the developer-UX-perspective, I'm not happy with this warning...
Specific use-case
I have a struct (simplified):
/// Raw SPI command interface for RFM95
pub struct Rfm95<Peripheral, Tx, Rx, Sck, Select, Reset>
where
Peripheral: SpiDevice,
Tx: PinId + ValidPinIdTx<Peripheral>,
Rx: PinId + ValidPinIdRx<Peripheral>,
Sck: PinId + ValidPinIdSck<Peripheral>,
Select: PinId,
Reset: PinId,
{
/// The SPI bus
bus: SpiBus<Peripheral, Tx, Rx, Sck>,
/// The chip select line
select: PinChipSelect<Select>,
/// The chip reset line
reset: PinReset<Reset>,
}
impl<Peripheral, Tx, Rx, Sck, Select, Reset> Rfm95<Peripheral, Tx, Rx, Sck, Select, Reset>
where
Peripheral: SpiDevice,
Tx: PinId + ValidPinIdTx<Peripheral>,
Rx: PinId + ValidPinIdRx<Peripheral>,
Sck: PinId + ValidPinIdSck<Peripheral>,
Select: PinId,
Reset: PinId,
{
/// The total FIFO size
const FIFO_SIZE: usize = 0xFF;
/// The register address to access the FIFO address pointer
const REGISTER_FIFO_ADDRESS_POINTER: u8 = 0x0D;
// ... implementation and member functions ...
}
Now, as naive developer, I tried to create an array in a member function
// Copy data into mutable buffer
let mut data_mut = [0; Self::FIFO_SIZE];
which raises this warning. I really don't understand why this restriction exists and this warning is emitted... the constant is fully constant; it's not dependent on any of the generic types, has no dynamic parts whatsoever, it's always 0xFF – it's so 100% boring, it could even be filled in using C-macro-style via search-replace.
Coming from a naive developer-UX-perspective, it's really not understandable why that's a problem at all. Now I'm sitting here without any reasonable workaround* (except free-floating constants or magic values), and that was so unsatisfying, I wrote this comment here 😅 IMO, this warning should either be dropped until there's a reasonable workaround; or the lint should link to/propose a reasonable workaround.
*If I missed something, please tell me and I'll happily heat my own words 😅
I think the intended work-around is to move the const FIFO_SIZE out of the impl block. 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.
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.
I don't really understand this part. As far as I see it, in most cases I can declare and use constants within generic structs pretty fine. I don't get a warning if I use FIFO_SIZE in conditional checks or REGISTER_FIFO_ADDRESS_POINTER; the only problem where this doesn't work is for array sizes and stuff.
And that feels inconsistent.
I think the intended work-around is to move the const FIFO_SIZE out of the impl block
Yup, it's the obvious fix, but that also feels more like a hack. Now I have to move this single const out of its associated context, while all other consts can stay in there and be used for constant operations (even compile-time assertions). At that point, I might as well hardcode the constant 😅
EDIT:
It appears even more inconsistent once you realize that you can easily do stuff like
impl<Peripheral, Tx, Rx, Sck, Select, Reset> Rfm95<Peripheral, Tx, Rx, Sck, Select, Reset>
where
Peripheral: SpiDevice,
Tx: PinId + ValidPinIdTx<Peripheral>,
Rx: PinId + ValidPinIdRx<Peripheral>,
Sck: PinId + ValidPinIdSck<Peripheral>,
Select: PinId,
Reset: PinId,
{
/// A default FIFO buffer "seed"
const FIFO_BUF_DEFAULT: [u8; 0xFF] = [0x00; 0xFF];
}
and use that instead, which works again.
Please note that I don't want to flame here; it's just that Rust makes great effort to be intuitive and developer-friendly, from error messages to 0-compromise correct APIs (e.g. Path). This one however feels out of place ("unrusty"), and reminds me more of C++-template magic and unexpected errors. So I thought this might be worth giving some feedback.
I don't really understand this part. As far as I see it, in most cases I can declare and use constants within generic structs pretty fine. I don't get a warning if I use FIFO_SIZE in conditional checks or REGISTER_FIFO_ADDRESS_POINTER; the only problem where this doesn't work is for array sizes and stuff.
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.
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.
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.)
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.
@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 { ... }
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).
@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
I've just hit this warning on current nightly in a project of mine, but I'm unsure the lint is actually supposed to fire here since there aren't any generics in scope.
The code (expanded) looked something like this:
static ON_BAR_CLICK: [f32; 2940] = const {
const BYTES: &[u8] = include_bytes!("../../assets/on_bar_click.pcm");
assert!(BYTES.len().is_multiple_of(4));
let mut f32s = [0.0; const { BYTES.len() / 4 }];
let mut i = 0;
while i < f32s.len() {
f32s[i] = f32::from_le_bytes([
BYTES[4 * i],
BYTES[4 * i + 1],
BYTES[4 * i + 2],
BYTES[4 * i + 3],
]);
i += 1;
}
f32s
};
Removing the const block around the array's length fixed the warning (https://github.com/generic-daw/generic-daw/commit/e1b6220ecc347aa43221ac41cfbf87252690166e), however I'm not sure the warning was actually supposed to fire here since there aren't any generics for the const block to capture, as far as I can tell.
I'm a bit confused on whether the triggering of this warning is expected behaviour or not.
Code, Errors and Questions
```rust
struct Identifier impl<const N: usize> Identifier<N> {
const NAMESPACE: &'static str = "default";
} impl<const N: usize> Decode for Identifier<N> {
async fn decode(...) -> Result<Self> { } 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 https://github.com/rust-lang/rust/issues/76200 This even works if If this is true: By declaring it inside the then neither of these implementations should work, but from a DX POV they should both work. ...
let mut namespace_buf = [0u8; Self::NAMESPACE.len() + 1];
...
}
Results in the error:
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];
...
}
}
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.
impl block, your const implicitly becomes dependent on all generic parameters of the type, and that's what triggers the warning.
If I missing something here please do correct me.
Using cargo 1.91.1 (ea2d97820 2025-10-10)
@Sycrosity can you provide a complete example where it does not lint?
@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)?,
))
}
}