Skip to content

generic_const_exprs does not work properly with const/type param defaults #106994

Open
@BoxyUwU

Description

@BoxyUwU
Member

In order to make feature(generic_const_exprs) not completely broken when used with feature(const_generics_defaults), #86580 was landed. From the description of the PR:

This PR doesn't handle the predicates of the const so

trait Foo<const N: usize> { const Assoc: usize; }
pub struct Bar<const N: usize = { <()>::Assoc }> where (): Foo<N>;

Resolves to <() as Foo<N>>::Assoc which can allow for using fwd declared params indirectly.

trait Foo<const N: usize> {}
struct Bar<const N: usize = { 2 + 3 }> where (): Foo<N>;

This code also ICEs under this PR because instantiating the default's predicates causes an ICE as predicates_of contains predicates with fwd declared params

because of this these two features are still pretty incompatible with eachother. This issue tracks this as there have been a large volume of issues that are all about which makes looking through F-generic_const-exprs issues harder than it needs to be.

It was previously attempted to fix this in #106847 but that was closed:

I don't feel super comfortable merging more hacks to make generic_const_exprs + const_generics_defaults work in the presence of each other. #86580 alone was already rather hacky and I think this PR just makes it way too much to be reasonable. It's also unclear to me whether this is going to interfere with fixing some of the other issues we have with const generics and I don't want to make that any harder than necessary.

feature(generic_const_exprs) is an incomplete feature (both literally and according to the incomplete_features lint), having it in a broken state because we are not currently in a good position to properly fix this seems fine to me. Thanks for making the PR anyway but I expect this issue will stay open until we make a lot more progress on generic_const_exprs 😅

duplicate issues

When this is fixed the following should be revisited and checked to make sure everything works as intended:

Activity

added
S-blockedStatus: Blocked on something else such as an RFC or other implementation work.
A-const-genericsArea: const generics (parameters and arguments)
on Jan 17, 2023
added
I-ICEIssue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️
T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
C-bugCategory: This is a bug.
on Jan 17, 2023
peter-lyons-kehl

peter-lyons-kehl commented on Jan 17, 2023

@peter-lyons-kehl
Contributor

Thank you for identifying this. And for any progress (hopefully).
Weeks of my proof-of-concept work on co-operative allocation in library/alloc depend on this. (I've referenced the old/duplicate of this issue in my commits; I'll reference this issue from new commits and rebase.)

Minimizing this even more - even without any declared trait:

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

struct V<const U: usize = {1+2}>
where
    [(); U]:;

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=971190f4162846600db09b2f4282f754

Update for ordinary Rust developers:
The above (ICE-triggering) example doesn't need feature generic_const_exprs - and without it, it does compile:

struct V<const U: usize = {1+2}>
where
    [(); U]:;

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=73bf77d07827c62dfe19ac9486e22983

So, if you need expressions in const generic defaults, but not in where bounds, there's a workaround:
Do not use generic_const_exprs. If you do need generic_const_exprs for other types, separate them into a separate crate, and make that generic_const_exprs-friendly crate consume/depend on the non-generic_const_exprs crate.

peter-lyons-kehl

peter-lyons-kehl commented on Feb 5, 2023

@peter-lyons-kehl
Contributor

Hoping to help narrow this closer: This fails even if the const expression has no arithmetics, just type casting, and that type casting is in the struct's generic parameters' default value expression. The casting itself may not even be needed (in this example, casting usize to usize):

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

struct FailingToCompile<const U: usize = 0usize as usize>
where
    [(); U]:;

Interestingly, if the const expression would normally require braces {...} (for example, because of invoking a macro), this ICE is triggered before checking for missing braces:

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

macro_rules! bound_size {
    () => {1usize}
}

struct BracesAreNOTcheckedBeforeICE<const U: usize = bound_size!() as usize>
where [(); U]:;

But, if there's no casting/transformation in the const generic param's default value (expression), then there's no ICE, and the checks for braces do get run:

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

macro_rules! bound_size {
    () => {1usize}
}

struct BracesAREcheckedIfNoICE<const U: usize = bound_size!()>
where [(); U]:;

If the same value is NOT cast in the generic params' list from a default value (or if it has no default value), but it's cast when instantiating the generic type, it DOES compile:

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

struct Compiles<const U: usize = 0>
where [(); U]:;

type T = Compiles<{1i32 as usize}>;

fn main() {
    let _t: T = T {};
}

However, if the struct's generic parameter list does have a default value BUT that expression does NOT involve casting/arithmetics (or any other transformations?), but the (generic param value) is cast in the where bound itself instead, it DOES compile:

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

struct Compiles<const I: i32 = 0>
where [(); I as usize]:;
{}

The result type of the const generic parameter's default value (expression) is NOT checked (to be the same as that const generic parameter's defined type) before the ICE happens:

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

struct IncorrectTypeOfDefaultValueIsNOTcheckedBeforeICE<const U: usize = {1u8 + 0i32 as usize}>
where
    [(); U]:;

But, when there is no where bound, such an incorrectly typed of the const generic parameter's default value IS checked:

struct TypeOfDefaultValueIScheckedIfNObounds<const U: usize = {1u8 + 0i32 as usize}>;
peter-lyons-kehl

peter-lyons-kehl commented on Feb 5, 2023

@peter-lyons-kehl
Contributor

What other combinations are worth checking (for someone not knowing deep rustc internals)? So that I can help this forward, please?

1 remaining item

peter-lyons-kehl

peter-lyons-kehl commented on Feb 7, 2023

@peter-lyons-kehl
Contributor

Still, while I'm narrowing this down from the "consumer's" point of view, and searching for workarounds, here are 2 examples.

  1. ICE, but ONLY if this generic type is actually used (in a function signature, an expression...). Otherwise the type definition itself does compile.
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

trait Tr {
    const C: usize = 0;
}

struct Str {}
impl Tr for Str {}

// ICE, but ONLY if this generic type is used (in a function signature, an expression...).
struct ICE<T: Tr = Str, const C: usize = {T::C}>
where [(); C]:,
{
    _t: core::marker::PhantomData<T>,
    _arr: [(); C],
}

// Using the above type in a function signature, or in an expression causes an ICE.

fn _type_in_fn_sig_return_causes_ice() -> ICE {
    loop{}
}
// and/or:

fn _type_in_fn_sig_param_causes_ice(_: &ICE) {
    loop{}
}
// and/or:

fn _instantiate() {
    ICE::<Str> {
        _t: core::marker::PhantomData {},
        _arr: []
    };
}
  1. ICE caused by mere presence of #![feature(generic_const_exprs)] itself. This code doesn't actually need generic_const_exprs. But, if the feature is enabled, then this code has an ICE.
#![allow(incomplete_features)]
// `struct ICE` below compiles well WITHOUT generic_const_exprs. But it does have an ICE with
// generic_const_exprs!
//
// The ICE is triggered even if the const generic-based struct itself (`ICE` below) is not used at
// all (not in any function signature, expression...). (And regardless of whether the type is
// public.)
#![feature(generic_const_exprs)]

trait Tr {
    const C: usize = 0;
}

struct Str {}
impl Tr for Str {}

struct ICE<const C: usize={Str::C}>
where [(); C]:,
{
    _arr: [(); C],
}
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-genericsArea: const generics (parameters and arguments)C-bugCategory: This is a bug.F-generic_const_exprs`#![feature(generic_const_exprs)]`I-ICEIssue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️P-lowLow priorityS-blockedStatus: Blocked on something else such as an RFC or other implementation work.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.requires-incomplete-featuresThis issue requires the use of incomplete features.requires-nightlyThis issue requires a nightly compiler in some way.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Participants

      @peter-lyons-kehl@fmease@BoxyUwU@workingjubilee

      Issue actions

        `generic_const_exprs` does not work properly with const/type param defaults · Issue #106994 · rust-lang/rust