Closed withoutboats closed 2 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.
{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 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.
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.
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 FIXME
s or will it require additional effort to implement?
@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.
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 (::< >
) .
@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.
@newpavlov I am surprised that code doesn't work. If you could submit a separate issue it would be appreciated.
@yodaldevoid @robarnold
Ah, got you. I wrongly assumed this is was relating to type/function signatures.
For using const generics on built-in array types, I've opened #60466 to see others' opinions on this.
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?
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.
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.
...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.
...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
@HadrienG2 See #60619 and #60632.
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<_, _>
:
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
@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?
[...] 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).
@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?
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>>
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 _
That's not it, the error message remains the same.
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
.
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.
@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.
@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.
@yodaldevoid Filed as https://github.com/rust-lang/rust/issues/61537 .
@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).
Oh, my bad then ! I guess I don't really know what I'm talking about.
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?
@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.
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>());
}
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?
factorials at compile-time with a Peano encoding,
For an example of that, see the typenum crate
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.
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) { }
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
@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.
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
@npmccallum you need to do Foo<{X}>
@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.
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.
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();
}
You snippet doesn't crash on playpen: https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=d3fda06d2e8b3eb739afa99d5da84a33
Same as #61338, the source of problem is incremental compilation.
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.
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:
FIXME(const_generics)
comments.FIXME(const_generics_defaults)
).has_infer_types
.{X * 2}
.