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

Helper trait hack const generics ICE #61383

Closed
est31 opened this issue May 31, 2019 · 24 comments
Closed

Helper trait hack const generics ICE #61383

est31 opened this issue May 31, 2019 · 24 comments
Assignees
Labels
A-const-generics Area: const generics (parameters and arguments) I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@est31
Copy link
Member

est31 commented May 31, 2019

In #60466 (comment), @rodrimati1992 shared a snippet to implement a trait only for numbers > 0. After a few modifications done by me, it gives you an ICE (playground):

#![feature(const_generics)] 

trait Foo {
    fn foo() -> Self;
}
impl<T, const N: usize> Foo for [T; N] 
where 
    Self:FooImpl<{N==0}>
{
    fn foo()->Self{
        Self::default_impl()
    }
}

trait FooImpl<const IS_ZERO:bool>{
    fn default_impl()->Self;
}

impl<T> FooImpl<{0u8==0u8}> for [T;0] {
    fn default_impl()->Self{
        []
    }
}

impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
where
    T:Default,
{
    fn default_impl()->Self{
        [T::default(); N]
    }
}
Compiler error message
error: internal compiler error: unexpected const parent in type_of_def_id(): TraitRef(TraitRef { path: path(FooImpl<>), hir_ref_id: HirId { owner: DefIndex(23), local_id: 10 } })

error: internal compiler error: cat_expr Errd
  --> src/main.rs:19:17
   |
19 | impl<T> FooImpl<{0u8==0u8}> for [T;0] {
   |                 ^^^^^^^^^^

error: internal compiler error: unexpected const parent in type_of_def_id(): TraitRef(TraitRef { path: path(FooImpl<>), hir_ref_id: HirId { owner: DefIndex(28), local_id: 17 } })

error: internal compiler error: cat_expr Errd
  --> src/main.rs:25:31
   |
25 | impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
   |                               ^^^^^^^^^^

error: internal compiler error: unexpected const parent in type_of_def_id(): TraitRef(TraitRef { path: path(FooImpl<>), hir_ref_id: HirId { owner: DefIndex(14), local_id: 16 } })

error: internal compiler error: cat_expr Errd
 --> src/main.rs:8:18
  |
8 |     Self:FooImpl<{N==0}>
  |                  ^^^^^^

error: internal compiler error: cat_expr Errd
  --> src/main.rs:29:28
   |
29 |       fn default_impl()->Self{
   |  ____________________________^
30 | |         [T::default(); N]
31 | |     }
   | |_____^

error: internal compiler error: cat_expr Errd
  --> src/main.rs:30:9
   |
30 |         [T::default(); N]
   |         ^^^^^^^^^^^^^^^^^

error: internal compiler error: mir_const_qualif: MIR had errors
 --> src/main.rs:8:18
  |
8 |     Self:FooImpl<{N==0}>
  |                  ^^^^^^

error: internal compiler error: QualifyAndPromoteConstants: MIR had errors
 --> src/main.rs:8:18
  |
8 |     Self:FooImpl<{N==0}>
  |                  ^^^^^^

error: internal compiler error: broken MIR in DefId(0:17 ~ playground[e253]::{{impl}}[0]::{{constant}}[0]) ("return type"): bad type [type error]
 --> src/main.rs:8:18
  |
8 |     Self:FooImpl<{N==0}>
  |                  ^^^^^^

error: internal compiler error: broken MIR in DefId(0:17 ~ playground[e253]::{{impl}}[0]::{{constant}}[0]) (LocalDecl { mutability: Mut, is_user_variable: None, internal: false, is_block_tail: None, ty: [type error], user_ty: UserTypeProjections { contents: [] }, name: None, source_info: SourceInfo { span: src/main.rs:8:18: 8:24, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
 --> src/main.rs:8:18
  |
8 |     Self:FooImpl<{N==0}>
  |                  ^^^^^^

error: internal compiler error: mir_const_qualif: MIR had errors
  --> src/main.rs:19:17
   |
19 | impl<T> FooImpl<{0u8==0u8}> for [T;0] {
   |                 ^^^^^^^^^^

error: internal compiler error: QualifyAndPromoteConstants: MIR had errors
  --> src/main.rs:19:17
   |
19 | impl<T> FooImpl<{0u8==0u8}> for [T;0] {
   |                 ^^^^^^^^^^

error: internal compiler error: broken MIR in DefId(0:25 ~ playground[e253]::{{impl}}[1]::{{constant}}[0]) ("return type"): bad type [type error]
  --> src/main.rs:19:17
   |
19 | impl<T> FooImpl<{0u8==0u8}> for [T;0] {
   |                 ^^^^^^^^^^

error: internal compiler error: broken MIR in DefId(0:25 ~ playground[e253]::{{impl}}[1]::{{constant}}[0]) (LocalDecl { mutability: Mut, is_user_variable: None, internal: false, is_block_tail: None, ty: [type error], user_ty: UserTypeProjections { contents: [] }, name: None, source_info: SourceInfo { span: src/main.rs:19:17: 19:27, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
  --> src/main.rs:19:17
   |
19 | impl<T> FooImpl<{0u8==0u8}> for [T;0] {
   |                 ^^^^^^^^^^

error: internal compiler error: mir_const_qualif: MIR had errors
  --> src/main.rs:25:31
   |
25 | impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
   |                               ^^^^^^^^^^

error: internal compiler error: QualifyAndPromoteConstants: MIR had errors
  --> src/main.rs:25:31
   |
25 | impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
   |                               ^^^^^^^^^^

error: internal compiler error: broken MIR in DefId(0:31 ~ playground[e253]::{{impl}}[2]::{{constant}}[0]) ("return type"): bad type [type error]
  --> src/main.rs:25:31
   |
25 | impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
   |                               ^^^^^^^^^^

error: internal compiler error: broken MIR in DefId(0:31 ~ playground[e253]::{{impl}}[2]::{{constant}}[0]) (LocalDecl { mutability: Mut, is_user_variable: None, internal: false, is_block_tail: None, ty: [type error], user_ty: UserTypeProjections { contents: [] }, name: None, source_info: SourceInfo { span: src/main.rs:25:31: 25:41, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
  --> src/main.rs:25:31
   |
25 | impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
   |                               ^^^^^^^^^^

error: internal compiler error: QualifyAndPromoteConstants: MIR had errors
  --> src/main.rs:29:5
   |
29 | /     fn default_impl()->Self{
30 | |         [T::default(); N]
31 | |     }
   | |_____^

error: internal compiler error: broken MIR in DefId(0:33 ~ playground[e253]::{{impl}}[2]::default_impl[0]) ("return type"): bad type [type error]
  --> src/main.rs:29:5
   |
29 | /     fn default_impl()->Self{
30 | |         [T::default(); N]
31 | |     }
   | |_____^

error: internal compiler error: broken MIR in DefId(0:33 ~ playground[e253]::{{impl}}[2]::default_impl[0]) (LocalDecl { mutability: Mut, is_user_variable: None, internal: false, is_block_tail: None, ty: [type error], user_ty: UserTypeProjections { contents: [] }, name: None, source_info: SourceInfo { span: src/main.rs:29:5: 31:6, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
  --> src/main.rs:29:5
   |
29 | /     fn default_impl()->Self{
30 | |         [T::default(); N]
31 | |     }
   | |_____^

thread 'rustc' panicked at 'no errors encountered even though `delay_span_bug` issued', src/librustc_errors/lib.rs:356:17
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

error: internal compiler error: unexpected panic

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

note: rustc 1.37.0-nightly (37d001e4d 2019-05-29) running on x86_64-unknown-linux-gnu

note: compiler flags: -C codegen-units=1 -C debuginfo=2 --crate-type bin

note: some of the compiler flags provided by cargo are hidden

error: Could not compile `playground`.

To learn more, run the command again with --verbose.
@varkor varkor added A-const-generics Area: const generics (parameters and arguments) I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 31, 2019
@varkor
Copy link
Member

varkor commented May 31, 2019

The discussion link is broken. Also, it'd be great if we could minimise this :)

@est31
Copy link
Member Author

est31 commented May 31, 2019

@varkor I've updated the discussion link. This is a minified version of the ICE:

trait FooImpl<const IS_ZERO: bool>{}

impl FooImpl<{0u8==0u8}> for () {}

@varkor
Copy link
Member

varkor commented May 31, 2019

So, I think #61409 mostly fixes this. However, we need #49147 to work for the entire helper impl to work.

@est31
Copy link
Member Author

est31 commented May 31, 2019

However, we need #49147 to work for the entire helper impl to work.

Why exactly? This would only affect us if Default it were const today, no? But it seems not to be const:

const fn foo() -> [u8; 32] {
    Default::default()
}

Gives:

error[E0723]: can only call other `const fn` within a `const fn`, but `const <[u8; 32] as std::default::Default>::default` is not stable as `const fn`
 --> src/main.rs:2:5
  |
2 |     Default::default()
  |     ^^^^^^^^^^^^^^^^^^
  |
  = note: for more information, see issue https://github.com/rust-lang/rust/issues/57563
  = help: add #![feature(const_fn)] to the crate attributes to enable

@HadrienG2
Copy link

HadrienG2 commented Jun 1, 2019

@est31 The thing is, for [x; N] to be valid today, x needs to be Copy. But the Default::default() in [Default::default(); N] is not Copy (unless we constrain it to be, which we probably don't want).

#49147 originally proposed relaxing this Copy constraint to accept any const expression as well, but as you point out, that will not be enough to accept Default::default() yet because not every Default impl is const (AFAIK, const impls are not even a thing yet).

What we need is therefore a further extension to the concept, discussed in #49147 by @eddyb, to accept any expression that can be repeatedly evaluated at runtime.

@est31
Copy link
Member Author

est31 commented Jun 1, 2019

@HadrienG2 oh interesting, yeah we don't require it to be Copy.

However, this is not the same as the thing @eddyb talked about. Lowering [Ok::<i32, String>(rand()); n] to let x = rand(); [Ok::<i32, String>(x); n] kinda works. But we don't have such a transparent case here but instead an opaque [Default::default(); n].

Consider a type struct Hello(String); whose default impl invokes String::new("Hello, World!"). You can't just clone the string pointers, you actually need to invoke Default n times.

It turns out that even if the type itself implements Copy, we can't lower to let v = Default::default(); [v; n], as you could have a type like struct UniqueUUID(u128); with default impl fn default() -> Self { Self(rand())}. Then an array of those becomes [UniqueUUID(rand()), UniqueUUID(rand()), UniqueUUID(rand()), ...] and ultimately this is different from let v = UniqueUUID(rand()); [v; n]. So we can't do any specializations for Copy types, those would be breaking changes.

Your comment @HadrienG2 made me realize that the snippet in the issue description won't ever be correct. But fortunately there is this simple solution (not requiring #49147):

impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
where
    T:Default,
{
    fn default_impl()->Self{
        let ret: Self = unsafe { core::mem::uninitialized() };
        let ret_slice: &mut [_] = ret;
        for e in ret_slice.iter_mut() {
            *e = Default::default();
        }
        ret
    }
}

It would probably be useful to have a function in libstd that wraps above code in a generic fashion, taking a closure impl Fn() -> T and calling the closure N times, returning [T;N]. And a try_ version that takes impl Fn() -> Result<T, E> and returns Result<[T;N], E>. This is just a side note however, the function is not required to implement this feature but it would allow you to avoid having to use unsafe yourself.

@HadrienG2
Copy link

HadrienG2 commented Jun 1, 2019

I suspect that as written, your snippet has enough opportunities for UB to give @RalfJung a heart attack. But I like its overall logic, this is how I typically go about initializing arrays of primitive types myself. Given a bit of massaging with pointers instead of references, MaybeUninit, and some thoughts around panic safety, it should be good to go.

@HadrienG2
Copy link

Note that the generic utility that you seek could take the form of an array-friendly form of Iterator::collect(), which would address one of my longstanding gripes with arrays in Rust...

@est31
Copy link
Member Author

est31 commented Jun 1, 2019

Note that the generic utility that you seek could take the form of an array-friendly form of Iterator::collect(), which would address one of my longstanding gripes with arrays in Rust...

You mean implementing FromIterator for Option<[T;N]>? That would be awesome! You should file a PR :).

@HadrienG2
Copy link

HadrienG2 commented Jun 1, 2019

Well, I'm not sure what exactly it should look like yet...

  1. FromIterator for Option<[T; N]>?
    • Should it try to detect that the iterator produces exactly N items, or only that it produces more than N?
    • In the former case, do we want to differentiate between "too many items" and "too few items"?
  2. FromIterator for [T; N]?
    • More convenient, especially when the size of an iterator is known in advance and Option forces a superfluous unwrap()... but this variant forces a panic on failure.
  3. ConstSizeIterator subtrait when size is guaranteed at compile time?
    • Allows getting the ergonomics of option 2 without the bad error handling in certain cases.
    • But will potentially cause significant duplication in Iterator traits.
    • Name needs bikeshedding too, ConstSize feels too similar to FixedSize.

...but now that const generics are around, we can at least talk about it, which was not an option before.

A PR would probably be frowned upon at this point in time, though, for the reasons outlined by @Centril in #60466 .

Shall we take this half-baked idea to internals for consolidation?

@est31
Copy link
Member Author

est31 commented Jun 1, 2019

Shall we take this half-baked idea to internals for consolidation?

Yeah that'd probably be the best place to discuss it. It's kinda off-topic to this thread. Saying this as the person who has started the discussion :).

@HadrienG2
Copy link

Here we go: https://internals.rust-lang.org/t/collecting-iterators-into-arrays/10330 .

@est31 est31 mentioned this issue Jun 3, 2019
6 tasks
Centril added a commit to Centril/rust that referenced this issue Jun 4, 2019
…=oli-obk

Fix an ICE with a const argument in a trait

This goes some way towards fixing rust-lang#61383 (the reduced test case is fixed).
Centril added a commit to Centril/rust that referenced this issue Jun 4, 2019
…=oli-obk

Fix an ICE with a const argument in a trait

This goes some way towards fixing rust-lang#61383 (the reduced test case is fixed).
Centril added a commit to Centril/rust that referenced this issue Jun 4, 2019
…=oli-obk

Fix an ICE with a const argument in a trait

This goes some way towards fixing rust-lang#61383 (the reduced test case is fixed).
@est31
Copy link
Member Author

est31 commented Jun 7, 2019

Okay, @varkor's PR #61409 has fixed the first minified example. The code in the issue description still ICEs. Minifying the error gives:

fn f<const N: usize>()->[();N]{
    [(); N]
}
error: internal compiler error: cat_expr Errd
  --> src/lib.rs:38:42
   |
38 |   fn default_impl<const N: usize>()->[();N]{
   |  __________________________________________^
39 | |     [(); N]
40 | | }
   | |_^

error: internal compiler error: cat_expr Errd
  --> src/lib.rs:39:5
   |
39 |     [(); N]
   |     ^^^^^^^

error: internal compiler error: QualifyAndPromoteConstants: MIR had errors
  --> src/lib.rs:38:1
   |
38 | / fn default_impl<const N: usize>()->[();N]{
39 | |     [(); N]
40 | | }
   | |_^

error: internal compiler error: broken MIR in DefId(0:12 ~ playground[92e4]::default_impl[0]) ("return type"): bad type [type error]
  --> src/lib.rs:38:1
   |
38 | / fn default_impl<const N: usize>()->[();N]{
39 | |     [(); N]
40 | | }
   | |_^

error: internal compiler error: broken MIR in DefId(0:12 ~ playground[92e4]::default_impl[0]) (LocalDecl { mutability: Mut, is_user_variable: None, internal: false, is_block_tail: None, ty: [type error], user_ty: UserTypeProjections { contents: [] }, name: None, source_info: SourceInfo { span: src/lib.rs:38:1: 40:2, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
  --> src/lib.rs:38:1
   |
38 | / fn default_impl<const N: usize>()->[();N]{
39 | |     [(); N]
40 | | }
   | |_^

thread 'rustc' panicked at 'no errors encountered even though `delay_span_bug` issued', src/librustc_errors/lib.rs:357:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

error: internal compiler error: unexpected panic

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

note: rustc 1.37.0-nightly (5eeb567a2 2019-06-06) running on x86_64-unknown-linux-gnu

@est31
Copy link
Member Author

est31 commented Jun 8, 2019

Oh I just realized that the current ICE it hits is probably the same as #61336.

@est31
Copy link
Member Author

est31 commented Jun 13, 2019

As of current 1.37.0-nightly (2887008 2019-06-12), the code snippet I pasted above doesn't ICE any more but instead gives a) a missing copy error and if that one is fixed by adding a trait bound, the error: array lengths can't depend on generic parameters error.

However, since then I've realized that it won't ever work because we don't require T to be copy and instead should call T::default() n many times. Thus, instead we should do something like this:

#![feature(const_generics)] 

trait Foo {
    fn foo() -> Self;
}
impl<T, const N: usize> Foo for [T; N] 
where 
    Self:FooImpl<{N==0}>
{
    fn foo()->Self{
        Self::default_impl()
    }
}

trait FooImpl<const IS_ZERO:bool>{
    fn default_impl()->Self;
}

impl<T> FooImpl<{0u8==0u8}> for [T;0] {
    fn default_impl()->Self{
        []
    }
}

impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
where
    T:Default,
{
    fn default_impl()->Self{
        unsafe {
            use std::mem::MaybeUninit;
            let mut res = MaybeUninit::<Self>::uninit();
            let res_mut_ptr = res.as_mut_ptr();
            for i in 0 .. N {
                *(res_mut_ptr.offset(i as isize) as * mut T) = T::default();
            }
            res.assume_init()
        }
    }
}

I hope this doesn't trigger any UB cases but @RalfJung may differ. Anyways, that code snippet compiles without errors so I guess everything is fine now.

@est31 est31 closed this as completed Jun 13, 2019
@RalfJung
Copy link
Member

RalfJung commented Jun 13, 2019

That's almost right, but the index calculation is fatally flawed... You are offseting at array type so you are offsetting by i times the full array!

It should be

    fn default_impl()->Self{
        unsafe {
            use std::mem::MaybeUninit;
            let mut res = MaybeUninit::<Self>::uninit();
            let res_mut_ptr = res.as_mut_ptr() as *mut T;
            for i in 0 .. N {
                *res_mut_ptr.add(i) = T::default();
            }
            res.assume_init()
        }
    }

This is why we need raw slice access methods...

@est31
Copy link
Member Author

est31 commented Jun 13, 2019

@RalfJung lol I haven't thought about that one. I have used to convert everything into a slice but I think that's very UB-y for partially uninitialized data.

After fixing it, trying to use it t gives an error now:

#![feature(const_generics)] 

trait Foo {
    fn foo() -> Self;
}
impl<T, const N: usize> Foo for [T; N] 
where 
    Self:FooImpl<{N==0}>
{
    fn foo()->Self{
        Self::default_impl()
    }
}

trait FooImpl<const IS_ZERO:bool>{
    fn default_impl()->Self;
}

impl<T> FooImpl<{0u8==0u8}> for [T;0] {
    fn default_impl()->Self{
        []
    }
}

impl<T,const N:usize> FooImpl<{0u8!=0u8}> for [T;N] 
where
    T:Default,
{
    fn default_impl()->Self{
        unsafe {
            use std::mem::MaybeUninit;
            let mut res = MaybeUninit::<Self>::uninit();
            let res_mut_ptr = res.as_mut_ptr() as *mut T;
            for i in 0 .. N {
                *res_mut_ptr.add(i) = T::default();
            }
            res.assume_init()
        }
    }
}

fn main() {
    let v: [u8; 64] = Foo::foo();
    let v_slice: &[u8] = &v;
    println!("{:?}", v_slice);
}

error[E0277]: the trait bound `[u8; 64]: FooImpl<{N==0}>` is not satisfied
  --> src/main.rs:43:23
   |
43 |     let v: [u8; 64] = Foo::foo();
   |                       ^^^^^^^^ the trait `FooImpl<{N==0}>` is not implemented for `[u8; 64]`
   |
   = help: the following implementations were found:
             <[T; 0] as FooImpl<true>>
             <[T; _] as FooImpl<false>>
   = note: required because of the requirements on the impl of `Foo` for `[u8; 64]`
note: required by `Foo::foo`
  --> src/main.rs:4:5
   |
4  |     fn foo() -> Self;
   |     ^^^^^^^^^^^^^^^^^

@varkor is this intended? Will this one day be implemented?

@est31
Copy link
Member Author

est31 commented Jun 13, 2019

Btw, I've found another ICE with code very similar to this one, but decided to file a separate issue to not get this one into scope creep: #61806

@varkor
Copy link
Member

varkor commented Jun 13, 2019

@varkor is this intended? Will this one day be implemented?

It's not intended, but I'm also not so surprised that it fails. There are still some issues we need to resolve regarding evaluation/unification of const generics.

@est31
Copy link
Member Author

est31 commented Jun 18, 2019

@varkor should I file an issue for it? Is there an issue I can track?

@varkor
Copy link
Member

varkor commented Jun 18, 2019

@est31: I don't think there's a specific tracking issue; it's worth opening one, so we have a test case.

@est31
Copy link
Member Author

est31 commented Jun 18, 2019

@varkor I'm est31, not estebank :). Will file an issue!

@varkor
Copy link
Member

varkor commented Jun 18, 2019

@est31: I shouldn't rely on autocorrect to fix my GitHub mentions 😅

@est31
Copy link
Member Author

est31 commented Jun 18, 2019

OK it's filed: #61935

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-generics Area: const generics (parameters and arguments) I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants