rust icon indicating copy to clipboard operation
rust copied to clipboard

Can't assign results of generic functions to `const` variables

Open moxian opened this issue 6 years ago • 10 comments

fn foo<T:Default>() {
    const bar : T = T::default();
}

gives the following error (twice for some reason):

$ cargo +nightly build
   Compiling arrys_sizeoof v0.1.0 (C:\work\trash\repros\arrys_sizeoof)
error[E0401]: can't use type parameters from outer function
 --> src\lib.rs:2:17
  |
1 | fn foo<T:Default>() {
  |    --- - type variable from outer function
  |    |
  |    try adding a local type parameter in this method instead
2 |     const bar : T = T::default();
  |                 ^ use of type variable from outer function

error[E0401]: can't use type parameters from outer function
 --> src\lib.rs:2:21
  |
1 | fn foo<T:Default>() {
  |    --- - type variable from outer function
  |    |
  |    try adding a local type parameter in this method instead
2 |     const bar : T = T::default();
  |                     ^^^^^^^^^^ use of type variable from outer function

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0401`.
error: Could not compile `arrys_sizeoof`.

To learn more, run the command again with --verbose.

Changing let to const to let makes it compile just fine.

Another, slightly different example:

fn get_size<T:Sized>() {
    const size : usize = std::mem::size_of::<T>();
}

Here, the type of the variable is non-generic now, and it's only a function that is. Results in the same error, but only once now:

$ cargo +nightly build
   Compiling arrys_sizeoof v0.1.0 (C:\work\trash\repros\arrys_sizeoof)
error[E0401]: can't use type parameters from outer function
 --> src\lib.rs:2:46
  |
1 | fn get_size<T:Sized>() {
  |    -------- - type variable from outer function
  |    |
  |    try adding a local type parameter in this method instead
2 |     const size : usize = std::mem::size_of::<T>();
  |                                              ^ use of type variable from outer function

error: aborting due to previous error

For more information about this error, try `rustc --explain E0401`.
error: Could not compile `arrys_sizeoof`.

To learn more, run the command again with --verbose.

P.s.: if this is the desired behavior, it is confusing, since 1) changing const to let should not result in type error, 2) rustc --explain E0401 doesn't mention anything close - all the examples are about definition of new functions/types inside the function body (and it's unclear how to adapt that knowledge to this particular use case).

P.p.s.: the behavior is exactly the same on current stable (1.32.0)

moxian avatar Jan 20 '19 06:01 moxian

Well, what's intended here is that you're not allowed to use type parameters of an enclosing item in an inner item. const, static, trait, fn, impl are items, while let is a statement, so it's allowed in the latter case. I don't see an actual typing error though. And it makes sense that the error only shows once in the second example, because there you only use T once and not twice.

jonas-schievink avatar Jan 20 '19 13:01 jonas-schievink

Yep; @jonas-schievink is correct. Rust does not support what is referred to in Haskell as ScopedTypeVariables. Moreover, even if you could use the type parameter T, calling T::default() would fail since it's not a const expression.

cc @estebank re. possible improvements to diagnostics.

cc https://github.com/rust-lang/rust/issues/57563.

Centril avatar Jan 20 '19 14:01 Centril

We need to stop suggesting adding a type argument when the requirement comes from a const, as they can't be generic, and replace it with a note stating that.

estebank avatar Jan 20 '19 16:01 estebank

From #48427, the same behavior is seen for statics

fn x<T: Default>() {
    static a: T = T::default();
}

https://github.com/rust-lang/rust/issues/48427#issuecomment-367676264:

I think the big problem would be crate A instantiating static a by invoking crate B's x function. If then crate C also instantiates static a by invoking crate B's x function, you end up with two different a. So if crate D depends on A and C, you'll get very weird behaviour. Might be solvable with MIR only rlibs, but it's generally really easy to get this wrong.

Note that you can get around this limitation by creating another trait bound on T:

trait Foo: 'static {
    fn singleton() -> &'static Self {
        unimplemented!()
    }
}

fn x<T: Default + Foo>() {
    let a = T::singleton();
}

Side-note: you won't be able to call T::default() in a static constructor anyway

estebank avatar May 23 '19 00:05 estebank

From #48427, the same behavior is seen for statics

@estebank It's the same for all nested items really; STV is the solution to all of them except for maybe impl items.

Centril avatar May 23 '19 04:05 Centril

Is there any news about this issue? Is it included in any roadmap? Can I do something to speed up its implementation? Especially for this case:

fn x<T: Default>() {
    static a: T = T::default();
}

Thanks.

zzau13 avatar Apr 07 '22 19:04 zzau13

There has been no movement in supporting this at the lang level, and the diagnostic could do with some love to be clearer.

estebank avatar Apr 08 '22 20:04 estebank

I also ran into this issue when I tried to do some compile-time assertions based on generic parameters from outer function. Here's one workaround I thought of, which I hope will help:

fn foo<T>() {
    // can't use generic parameters from outer function:
    // const ASSERT: () = assert!(mem::size_of::<T>() == 8);
    let _ = Assert::<T>::SIZE_EQ_8;
}

struct Assert<T> {
    _marker: PhantomData<T>,
}

impl<T> Assert<T> {
    const SIZE_EQ_8: () = assert!(mem::size_of::<T>() == 8);
}

yescallop avatar Apr 13 '22 12:04 yescallop

I can confirm that sometimes it leads to very strange compilation errors. This code compiles fine:

use core::marker::PhantomData;

struct A<T>(PhantomData<T>);
impl<T> A<T> {
    const EMPTY: Vec<T> = Vec::new();
    
    fn f() -> [Vec<T>; 2] {
        [Self::EMPTY; 2]
    }
}

fn main() {
    let empty = A::<u32>::f();
    dbg!(empty);
}

while this does not:

fn f<U>() -> [Vec<U>; 2] {
    const EMPTY: Vec<U> = Vec::new();
    [EMPTY; 2]
}

fn main() {
    let empty = f::<u32>();
    dbg!(empty);
}

Ternvein avatar Nov 08 '22 19:11 Ternvein

@Ternvein, that is expected. While free constant items may not reference the generics of the outer item, associated constant items are allowed to reference the generics of their direct parent (i.e., trait, trait impl or impl).

fmease avatar Feb 02 '24 16:02 fmease