rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.59k stars 12.74k forks source link

Tracking issue for const generics (RFC 2000) #44580

Closed withoutboats closed 2 years ago

withoutboats commented 7 years ago

Tracking issue for rust-lang/rfcs#2000

Updates:

If you want to help out, take a look at the open const generics issues and feel free to ping @varkor, @eddyb, @yodaldevoid, @oli-obk or @lcnr for help in getting started!


Blocking stabilization:


Remaining implementation issues:

bjorn3 commented 5 years ago

@mark-i-m try putting {} around FOO. That way it will work.

Quoting https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md:

When applying an expression as const parameter (except for arrays), which is not an identity expression, the expression must be contained within a block. This syntactic restriction is necessary to avoid requiring infinite lookahead when parsing an expression inside of a type.

rodrimati1992 commented 5 years ago

{expression} should only be necessary if the expression is not an identifier or a literal,that is a bug.

Quoting the same RFC:

Identity expression: An expression which cannot be evaluated further except by substituting it with names in scope. This includes all literals as well all idents - e.g. 3, "Hello, world", foo_bar.

mark-i-m commented 5 years ago

:+1: Thanks. I just found https://github.com/rust-lang/rust/blob/ef9a876f8297e26859469b004047e0946ed10565/src/libsyntax/parse/parser.rs#L6056

yodaldevoid commented 5 years ago

@mark-i-m Yes, currently we cannot tell the difference between idents for types and consts when initially parsing. We punted the decision on how to handle all that into the future. I suppose now might be the future.

For a little background, we have talked about two ways that I can remember about how to handle this. The first way is to force people to mark const arguments (either by typing const before them or some other way). This isn't great from an ergonomics point of view, but it is easy from a parsing point of view. The second way is to treat all generic arguments as the same until we start pairing them up with generic parameters later during compilation. This is probably the method we want to take, and there has been some work to make this possible already, we just haven't taken the final leap.

BenLewis-Seequent commented 5 years ago

Amazing work. I'm happy to help out, by fixing some of the FIXME's, if I can. I don't have much experience within the rustc codebase at all, so I'll start on the FIXME https://github.com/rust-lang/rust/blob/master/src/librustc_mir/monomorphize/item.rs#L397, as it's seems like it would be an easy one.

newpavlov commented 5 years ago

What about the following code:

trait Foo {
    const N: usize;
    fn foo() -> [u8; Self::N];
}

Currently it results in "no associated item named N found for type Self in the current scope" compilation error. Will such code be accepted after finishing FIXMEs or will it require additional effort to implement?

zesterer commented 5 years ago

@yodaldevoid

Quick question, apologies if this has already been discussed.

The second way is to treat all generic arguments as the same until we start pairing them up with generic parameters later during compilation. This is probably the method we want to take, and there has been some work to make this possible already, we just haven't taken the final leap.

Does this not run rather against the grain on the principle Rust takes on making function signatures explicit to avoid producing errors that relate to a function's implementation? Perhaps I'm totally misunderstanding this however and you're talking about parsing generic parameters within a function's body.

rodrimati1992 commented 5 years ago

Does this not run rather against the grain on the principle Rust takes on making function signatures explicit to avoid producing errors that relate to a function's implementation? Perhaps I'm totally misunderstanding this however and you're talking about parsing generic parameters within a function's body.

This is about determining whether identifiers passed as generic parameters to a function are constants or types.

Example:

fn greet<const NAME:&'static str>(){
    println!("Hello, {}.",NAME);
}
const HIS_NAME:&'static str="John";
greet::<HIS_NAME>();
greet::<"Dave">();

Note that when defining the function,you have to specify that NAME is a constant,but when you call it,it's not necessary to say that HIS_NAME is a constant within the turbofish operator (::< >) .

yodaldevoid commented 5 years ago

@zesterer If this proposed solution was made transparent to the user (e.g. when defining a function there was no difference between parameter types) you would be correct. However the idea behind the proposed solution is not to change the user facing behavior, but rather to only change the implementation. The user would still write out function signatures with explicit type, const, and lifetime parameters, and generic arguments would still need to match up to a corresponding parameter, just how we parse it in the compiler would change.

yodaldevoid commented 5 years ago

@newpavlov I am surprised that code doesn't work. If you could submit a separate issue it would be appreciated.

zesterer commented 5 years ago

@yodaldevoid @robarnold

Ah, got you. I wrongly assumed this is was relating to type/function signatures.

crlf0710 commented 5 years ago

For using const generics on built-in array types, I've opened #60466 to see others' opinions on this.

HadrienG2 commented 5 years ago

Impl blocks seem completely broken at the moment. Is this expected in the current state of the feature, or should I open another issue about it?

JamesHinshelwood commented 5 years ago

Impl blocks seem completely broken at the moment. Is this expected in the current state of the feature, or should I open another issue about it?

You need to add {}s around the N, impl<const N: usize> Dummy<{N}> {}.

But yeah then you'll get an error about the unconstrained parameter.

HadrienG2 commented 5 years ago

Ah, thanks. Forgot about the braces thing !

I'm not surprised that it fails once that is resolved, since this was a heavily minimized test case.

HadrienG2 commented 5 years ago

...but yeah, this one should probably work:

#![feature(const_generics)]

trait Dummy {}

struct Vector<const N: usize> {
    data: [f32; N],
}

impl<const N: usize> Dummy for Vector<{N}> {}

...and it still fails with E0207:

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/lib.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error[E0207]: the const parameter `N` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:9:12
  |
9 | impl<const N: usize> Dummy for Vector<{N}> {}
  |            ^ unconstrained const parameter

error: aborting due to previous error

For more information about this error, try `rustc --explain E0207`.
error: Could not compile `small-matrix`.

To learn more, run the command again with --verbose.
HadrienG2 commented 5 years ago

...and I assume this is what @varkor meant by "issues with array handling for consts generics" in #60466:

#![feature(const_generics)]

fn dummy<const N: usize>() -> [f32; N] {
    [0.; N]
}

->

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/lib.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error: internal compiler error: cat_expr Errd
 --> src/lib.rs:3:44
  |
3 |   pub fn dummy<const N: usize>() -> [f32; N] {
  |  ____________________________________________^
4 | |     [0.; N]
5 | | }
  | |_^

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

error: internal compiler error: QualifyAndPromoteConstants: Mir had errors
 --> src/lib.rs:3:1
  |
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | |     [0.; N]
5 | | }
  | |_^

error: internal compiler error: broken MIR in DefId(0/0:3 ~ small_matrix[5b35]::dummy[0]) ("return type"): bad type [type error]
 --> src/lib.rs:3:1
  |
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | |     [0.; N]
5 | | }
  | |_^

error: internal compiler error: broken MIR in DefId(0/0:3 ~ small_matrix[5b35]::dummy[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:3:1: 5:2, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
 --> src/lib.rs:3:1
  |
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | |     [0.; N]
5 | | }
  | |_^

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.36.0-nightly (cfdc84a00 2019-05-07) running on x86_64-unknown-linux-gnu

note: compiler flags: -C debuginfo=2 -C incremental --crate-type lib

note: some of the compiler flags provided by cargo are hidden
newpavlov commented 5 years ago

@HadrienG2 See #60619 and #60632.

crlf0710 commented 5 years ago

I think this code should build: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e45b7b5e881732ad80b7015fc2d3795c

but it currently errors:

error[E0119]: conflicting implementations of trait std::convert::TryFrom<[type error]> for type MyArray<_, _>:

oberien commented 5 years ago

Unfortunately that's a restriction of Try{From,Into} and doesn't have anything to do with const generics. They can't be implemented generically in many cases: playpen

crlf0710 commented 5 years ago

@oberien in your example, T is a foreign type, and it's not known whether T: Into<Foo<T>> holds or not.

In my example, [T; N] is a type defined in libcore, and is #[fundamental](tbh i don't even know what this means), so i think the trait resolver actually knows [T; N]: Into<MyArray<T, {N}>> does not hold, and there should not be a conflict, i think?

Nemo157 commented 5 years ago

[...] and is #[fundamental] [...]

This is the issue, fundamental is related to allowing downstream users to define trait implementations when they wrap a local type in the fundamental wrapper type. You can see in this playground that your implementation works between non-fundamental types, but fails if the from type is marked fundamental (with a much better error message).

HadrienG2 commented 5 years ago

@varkor @yodaldevoid So, I just ended up in a situation where S<{N == 0}> (with S taking a const bool parameter and N a const usize) is neither S<{true}> or S<{false}> in the eye of the compiler (in the sense that it does not implement the same traits as either), and am not sure if this is a bug or an expected limitation of the current prototype. Can you give me a quick refresher on the current unification logic?

HadrienG2 commented 5 years ago

As a minimized example, this...

// Machinery for type level if-then-else
struct Test<const B: bool>;
trait IfFn<S, Z> { type Out; }
impl<S, Z> IfFn<S, Z> for Test<{true }> { type Out = S; }
impl<S, Z> IfFn<S, Z> for Test<{false}> { type Out = Z; }

// Returns an u8 if B is true, else an u16
fn should_be_ok<const B: bool>() -> <Test<{B}> as IfFn<u8, u16>>::Out {
    0
}

...produces the following error:

error[E0277]: the trait bound `Test<B>: IfFn<u8, u16>` is not satisfied
  --> src/lib.rs:32:1
   |
32 | / fn should_be_ok<const B: bool>() -> <Test<{B}> as IfFn<u8, u16>>::Out {
33 | |     0
34 | | }
   | |_^ the trait `IfFn<u8, u16>` is not implemented for `Test<B>`
   |
   = help: the following implementations were found:
             <Test<false> as IfFn<S, Z>>
             <Test<true> as IfFn<S, Z>>
programmerjake commented 5 years ago

I don't actually know, but I think the problem may be that the compiler doesn't have the logic to decide if the 0 literal is a u8 or a u16. Try something like: 0u8 as _

HadrienG2 commented 5 years ago

That's not it, the error message remains the same.

HadrienG2 commented 5 years ago

But if you want a version that does not involve integer type inference, here's a sillier minimization:

struct Test<const B: bool>;
trait Not { const B: bool; }
impl Not for Test<{true }> { const B: bool = false; }
impl Not for Test<{false}> { const B: bool = true; }

fn should_be_ok<const B: bool>() -> bool {
    <Test<{B}> as Not>::B
}

Still fails due to Test<{B}> allegedly not implementing Not.

carado commented 5 years ago

Hi ! I'm not really sure whether unification on const values is supposed to work or not, but there is a new unification system in the works ("chalk") that is being worked on but isn't what rustc currently uses. So what you're trying to do may have to wait until chalk is ready. And even then, I'm not sure it will work or be supposed to work even with chalk. See https://github.com/rust-lang/rust/issues/48049 for progress.

yodaldevoid commented 5 years ago

@HadrienG2 could you file a separate issue for that? I'm not entirely surprised that doesn't work at the moment due to a few other issues that we are looking into, but this is a new case and I'd like to have it tracked.

@carado Unification is done on consts or none of this would work. Const generics are not blocked on chalk.

est31 commented 5 years ago

@HadrienG2 once we have specialization for const generics, you'll be able to do something like

struct Test<const B: bool>;
trait Not { const B: bool; }
impl<const B: bool> Not for Test<B> { const B: bool = false; }
impl Not for Test<{false}> { const B: bool = true; }

fn should_be_ok<const B: bool>() -> bool {
    <Test<{B}> as Not>::B
}

What you want to do is some kind of exhaustiveness check like there is for match just extended to the trait system. Don't know of it being mentioned in any thread.

HadrienG2 commented 5 years ago

@yodaldevoid Filed as https://github.com/rust-lang/rust/issues/61537 .

eddyb commented 5 years ago

@carado Chalk doesn't concern the actual "typesystem primitives", which this is one of. That is, rustc still has to (even today, since Chalk is already partly integrated) implement the part of the unification which deals with entities opaque to Chalk (such as two different constant expressions). If we implement that (which we don't have any near-future plans for, anyway), it won't change much even after Chalk replaces the trait system (which is its main purpose, not unification).

carado commented 5 years ago

Oh, my bad then ! I guess I don't really know what I'm talking about.

dancojocaru2000 commented 5 years ago

One of the awesome things that const generics would provide is compile time computed simple functions, for example factorial.

Example:

fn factorial<const X: i32>() -> Option<i32> {
    match X {
        i if i < 0 => None,
        0 => Some(1),
        1 => Some(1),
        i => Some(factorial::<i - 1>().unwrap() + i)
    }
}

As far as I understand, there is some limited support for const generics in nightly? If so, is there any place more organised than this issue where I can find how to use it and stuff?

lnicola commented 5 years ago

@dancojocaru2000 const functions should be the preferred way for value-level computation at compile-time, e.g. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4994b7ca9cda0bfc44f5359443431378, which happens not to work because they're not fully implemented yet. But neither are const generics.

You snippet might be problematic because I'm not sure whether match is supposed to work for const arguments. You also mixed addition with multiplication in the code, but that doesn't matter too much.

I think you can already do factorials at compile-time with a Peano encoding, which is often shown as a demo for other functional languages.

shepmaster commented 5 years ago

compile time computed simple functions

I think the more appropriate feature would be const fn.

Theoretically, your code would work almost as-is

#![feature(const_generics)]

fn factorial<const X: i32>() -> Option<i32> {
    match X {
        i if i < 0 => None,
        0 => Some(1),
        1 => Some(1),
        i => Some(factorial::<{X - 1}>().unwrap() + i)
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}

But it doesn't:

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/main.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error: internal compiler error: src/librustc_codegen_ssa/mir/operand.rs:79: unevaluated constant in `OperandRef::from_const`

Although you really should use an unsigned type:

#![feature(const_generics)]

fn factorial<const X: u32>() -> u32 {
    match X {
        0 => 1,
        1 => 1,
        _ => factorial::<{ X - 1 }>() + X,
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}
shepmaster commented 5 years ago

is there any place more organised than this issue where I can find how to use it and stuff?

That's usually covered by the unstable book, but there's nothing useful there now. As you discover bits, perhaps you could consider starting to sketch out some content for that?

shepmaster commented 5 years ago

factorials at compile-time with a Peano encoding,

For an example of that, see the typenum crate

bjorn3 commented 5 years ago

Theoretically, your code would work almost as-is

When calling factorial::<0> the _ => factorial::<{X - 1}>() * X would be codegened too right? But attempting to do so would cause an integer underflow.

peterjoel commented 5 years ago

Can I expect code like this to compile in the future?

#![feature(const_generics)]

trait NeedsDrop<const B: bool> { }
impl<T> NeedsDrop<std::mem::needs_drop<T>()> for T { }

fn foo<D: NeedsDrop<false>>(d: D) { }
shamatar commented 5 years ago

This is crossing with a recent implementation of some [T; N] arrays for N <= 32 as const generics, but the following code does not compile on a latest (4bb6b4a5e 2019-07-11) nightly with various errors the trait `std::array::LengthAtMost32` is not implemented for `[u64; _]`

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BigintRepresentation<
    const N: usize
>(pub [u64; N]) 
where [u64; N]: std::array::LengthAtMost32, 
   [u64; N*2]: std::array::LengthAtMost32;

although the following does:

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BigintRepresentation<
    const N: usize
>(pub [u64; N]) 
where [u64; N]: std::array::LengthAtMost32;

Looks like N*2 is not a valid const or qualifier for such constraint

pengowen123 commented 5 years ago

@shamatar N*2 should be surrounded with curly braces like `[u64; {N*2}]

I think the "trait not implemented" errors come from the derive macros not adding the bound for [u64; {N*2}], but if the derives are removed there is an ICE currently:


 --> src/main.rs:5:1
  |
5 | / pub struct BigintRepresentation<
6 | |     const N: usize
7 | | >(pub [u64; N]) 
8 | | where [u64; N]: std::array::LengthAtMost32,
9 | |    [u64; {N*2}]: std::array::LengthAtMost32;
  | |____________________________________________^

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

This code does not compile:

#![feature(const_generics)]

struct Foo<const X: usize>([u8; X]);

impl<const X: usize> Foo<X> {
    fn new() -> Self {
        Self([0u8; X])
    }
}
warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/lib.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error[E0573]: expected type, found const parameter `X`
 --> src/lib.rs:5:26
  |
5 | impl<const X: usize> Foo<X> {
  |                          ^
  |                          |
  |                          not a type
  |                          help: try using the variant's enum: `regex_syntax::ast::HexLiteralKind`

error[E0107]: wrong number of const arguments: expected 1, found 0
 --> src/lib.rs:5:22
  |
5 | impl<const X: usize> Foo<X> {
  |                      ^^^^^^ expected 1 const argument

error[E0107]: wrong number of type arguments: expected 0, found 1
 --> src/lib.rs:5:26
  |
5 | impl<const X: usize> Foo<X> {
  |                          ^ unexpected type argument

error: aborting due to 3 previous errors
est31 commented 5 years ago

@npmccallum you need to do Foo<{X}>

shamatar commented 5 years ago

@pengowen123 Yes, it compiles (crashes the compiler with constant in type had an ignored error: TooGeneric) without derive, but then it may be a separate question whether or not such "double constraint" that is effectively N <= 32 and N <= 16 is not applied by compiler.

eddyb commented 5 years ago

Expressions that mention parameters probably won't work for a while longer, [T; N] and Foo<{N}> are special-cased to not have expressions with a mention of N in them, but rather directly refer to the N parameter, bypassing the larger issue.

Jezza commented 5 years ago

Using Self causes a crash. Even swapping it out for Value<{C}>, it still crashes.

#![feature(const_generics)]

struct Value<const C: usize>;

impl<const C: usize> Value<{C}> {
    pub fn new() -> Self {
        unimplemented!()
    }
}

pub fn main() {
    let value = Value::new();
}
tesuji commented 5 years ago

You snippet doesn't crash on playpen: https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=d3fda06d2e8b3eb739afa99d5da84a33

c410-f3r commented 5 years ago

Same as #61338, the source of problem is incremental compilation.

hameerabbasi commented 5 years ago

compile time computed simple functions

I think the more appropriate feature would be const fn.

Theoretically, your code would work almost as-is

#![feature(const_generics)]

fn factorial<const X: i32>() -> Option<i32> {
    match X {
        i if i < 0 => None,
        0 => Some(1),
        1 => Some(1),
        i => Some(factorial::<{X - 1}>().unwrap() + i)
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}

But it doesn't:

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/main.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error: internal compiler error: src/librustc_codegen_ssa/mir/operand.rs:79: unevaluated constant in `OperandRef::from_const`

Although you really should use an unsigned type:

#![feature(const_generics)]

fn factorial<const X: u32>() -> u32 {
    match X {
        0 => 1,
        1 => 1,
        _ => factorial::<{ X - 1 }>() + X,
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}

This feature is useful for me too, const fn isn't enough. I want to use it for an N-dimensional array with a terminating condition of dimensions=0.