Skip to content

Split const generics in two and document inferred consts #1835

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
- [Impl trait type](types/impl-trait.md)
- [Type parameters](types/parameters.md)
- [Inferred type](types/inferred.md)
- [Const Generics](const-generics.md)
- [Dynamically Sized Types](dynamically-sized-types.md)
- [Type layout](type-layout.md)
- [Interior mutability](interior-mutability.md)
Expand Down
174 changes: 174 additions & 0 deletions src/const-generics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
r[const-generics]
# Const Generics

r[const-generics.argument]
A const argument in a [path] specifies the const value to use for that item.

r[const-generics.argument.type]
The argument must be a [const expression] of the type ascribed to the const
parameter.

r[items.generics.const.type-ambiguity]
When there is ambiguity if a generic argument could be resolved as either a
type or const argument, it is always resolved as a type. Placing the argument
in a block expression can force it to be interpreted as a const argument.

<!-- TODO: Rewrite the paragraph above to be in terms of namespaces, once
namespaces are introduced, and it is clear which namespace each parameter
lives in. -->

```rust,compile_fail
type N = u32;
struct Foo<const N: usize>;
// The following is an error, because `N` is interpreted as the type alias `N`.
fn foo<const N: usize>() -> Foo<N> { todo!() } // ERROR
// Can be fixed by wrapping in braces to force it to be interpreted as the `N`
// const parameter:
fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok
```

r[items.generics.const.exhaustiveness]
When resolving a trait bound obligation, the exhaustiveness of all
implementations of const parameters is not considered when determining if the
bound is satisfied. For example, in the following, even though all possible
const values for the `bool` type are implemented, it is still an error that
the trait bound is not satisfied:

```rust,compile_fail
struct Foo<const B: bool>;
trait Bar {}
impl Bar for Foo<true> {}
impl Bar for Foo<false> {}

fn needs_bar(_: impl Bar) {}
fn generic<const B: bool>() {
let v = Foo::<B>;
needs_bar(v); // ERROR: trait bound `Foo<B>: Bar` is not satisfied
}
```

r[const-generics.kinds]
There are three kinds of arguments to a const parameter:
1. Standalone const parameters
2. Inferred consts
3. Arbitrary concrete expressions

r[const-generics.standalone]
## Standalone const parameters

A const parameter can only be used in a const argument if it is a standalone usage.
The argument must be *only* a usage of a const parameter and can be wrapped
in at most one level of braces. That is, they cannot be combined with other
expressions.
```rust
// Examples of standalone uses of const parameters

fn foo<const N: usize>() {
let a: [u8; N] = [10; N];
let b: [u8; { N }] = a;
foo::<N>();
foo::<{ N }>();
}
```

Here `a` has type `[u8; N]`, an array with a length of `N`, referring to `foo`'s const parameter.

```rust,compile_fail
// Examples of non-standalone uses of const parameters

fn foo<const N: usize>() {
let a: [u8; {{ N }}] = [10; (N)];
foo::<{{ N }}>();
foo::<(N)>();
}
```

r[const-generics.inferred]
## Inferred consts

r[const-generics.inferred.syntax]
```grammar,types
@root InferredConst -> `_`
Copy link
Member Author

@BoxyUwU BoxyUwU May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know how to use this grammar,types syntax to explain that you can do (((_))) too

```

The inferred const asks the compiler to infer the const if possible based on
the surrounding information available.

It cannot be used in item signatures.

It is often used in repeat expressions:
```rust
fn make_array() -> [u32; 2] {
[Default::default(); _]
}
```

r[const-generics.concrete-expr]
## Concrete expressions
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I left out here is that the "concrete expressions" are not required to be quite so concrete for repeat expr counts as a backwards compatibility FCW. Does the reference intend to document the actual state of the compiler, or can I take liberties in documenting things as if FCWs are already hard errors?


Most const expressions are allowed as const arguments:
```rust
// Example of a concrete expressions as an argument

fn make_array() -> [u8; 1 + 10 / 2] {
[1; 6]
}
```

r[const-generics.concrete-expr.limitations]
There are a few limitations about what expressions are allowed:
1. Generic parameters may not be used
2. In-scope where clauses may not be used
3. Must be wrapped in braces in some cases

```rust,compile_fail
// Examples where expressions may not be used as they use generic parameters.

// Not allowed in the const argument for an arrays length
fn bad_function<const N: usize>() -> [u8; N + 1] {
// Similarly not allowed for array repeat expressions' count argument.
[1; N + 1]
}

// Using type parameters is also disallowed
fn type_parameters_disallowed<T>(_: [u8; size_of::<T>()]) {}
```

```rust,compile_fail
// Example where an expression may not be used as it depends on an in-scope
// where clause

fn bad_function(_: [u8; { let a: [u8]; 1 }])
where
for<'a> [u8]: Sized, {}
```

The const expression must be a [block expression][block](surrounded with braces) unless it's:
- a single path segment (an [IDENTIFIER])
- a [literal] (with a possibly leading `-` token)
- an array length or repeat expression count

> [!NOTE]
> This syntactic restriction is necessary to avoid requiring infinite lookahead when parsing an expression inside of a type.

```rust
fn double<const N: i32>() {
println!("doubled: {}", N * 2);
}

const SOME_CONST: i32 = 12;

fn example() {
// Example usage of a const argument.
double::<9>();
double::<-123>();
double::<{7 + 8}>();
double::<SOME_CONST>();
double::<{ SOME_CONST + 5 }>();
}
```

[block]: ../expressions/block-expr.md
[const expression]: ../const_eval.md#constant-expressions
[literal]: ../expressions/literal-expr.md
[path]: ../paths.md
122 changes: 14 additions & 108 deletions src/items/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,36 +67,26 @@ The only allowed types of const parameters are `u8`, `u16`, `u32`, `u64`, `u128`
`i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `char` and `bool`.

r[items.generics.const.usage]
Const parameters can be used anywhere a [const item] can be used, with the
exception that when used in a [type] or [array repeat expression], it must be
standalone (as described below). That is, they are allowed in the following
places:

1. As an applied const to any type which forms a part of the signature of the
item in question.
2. As part of a const expression used to define an [associated const], or as a
parameter to an [associated type].
3. As a value in any runtime expression in the body of any functions in the
item.
4. As a parameter to any type used in the body of any functions in the item.
5. As a part of the type of any fields in the item.
Const parameters can be used anywhere in expression position, or as a const argument[^1].
Uses of const parameters must take place inside the item defining it, and not within
an inner item that inhibits access to outer generic parameters.

```rust
// Examples where const generic parameters can be used.

// Used in the signature of the item itself.
fn foo<const N: usize>(arr: [i32; N]) {
// Used as a type within a function body.
// Used within a type within a function body.
let x: [i32; N];
// Used as an expression.
// Used within an expression.
println!("{}", N * 2);
}

// Used as a field of a struct.
// Used within a field of a struct.
struct Foo<const N: usize>([i32; N]);

impl<const N: usize> Foo<N> {
// Used as an associated constant.
// Used within an associated constant.
const CONST: usize = N * 4;
}

Expand All @@ -105,13 +95,14 @@ trait Trait {
}

impl<const N: usize> Trait for Foo<N> {
// Used as an associated type.
// Used within an associated type.
type Output = [i32; N];
}
```

```rust,compile_fail
// Examples where const generic parameters cannot be used.
// Examples where const generic parameters cannot be used as the uses are
// within an inner item that inhibits access to outer generic parameters.
fn foo<const N: usize>() {
// Cannot use in item definitions within a function body.
const BAD_CONST: [usize; N] = [1; N];
Expand All @@ -124,72 +115,6 @@ fn foo<const N: usize>() {
}
```

r[items.generics.const.standalone]
As a further restriction, const parameters may only appear as a standalone
argument inside of a [type] or [array repeat expression]. In those contexts,
they may only be used as a single segment [path expression], possibly inside a
[block] (such as `N` or `{N}`). That is, they cannot be combined with other
expressions.

```rust,compile_fail
// Examples where const parameters may not be used.

// Not allowed to combine in other expressions in types, such as the
// arithmetic expression in the return type here.
fn bad_function<const N: usize>() -> [u8; {N + 1}] {
// Similarly not allowed for array repeat expressions.
[1; {N + 1}]
}
```

r[items.generics.const.argument]
A const argument in a [path] specifies the const value to use for that item.

r[items.generics.const.argument.const-expr]
The argument must be a [const expression] of the type ascribed to the const
parameter. The const expression must be a [block expression][block]
(surrounded with braces) unless it is a single path segment (an [IDENTIFIER])
or a [literal] (with a possibly leading `-` token).

> [!NOTE]
> This syntactic restriction is necessary to avoid requiring infinite lookahead when parsing an expression inside of a type.

```rust
fn double<const N: i32>() {
println!("doubled: {}", N * 2);
}

const SOME_CONST: i32 = 12;

fn example() {
// Example usage of a const argument.
double::<9>();
double::<-123>();
double::<{7 + 8}>();
double::<SOME_CONST>();
double::<{ SOME_CONST + 5 }>();
}
```

r[items.generics.const.type-ambiguity]
When there is ambiguity if a generic argument could be resolved as either a
type or const argument, it is always resolved as a type. Placing the argument
in a block expression can force it to be interpreted as a const argument.

<!-- TODO: Rewrite the paragraph above to be in terms of namespaces, once
namespaces are introduced, and it is clear which namespace each parameter
lives in. -->

```rust,compile_fail
type N = u32;
struct Foo<const N: usize>;
// The following is an error, because `N` is interpreted as the type alias `N`.
fn foo<const N: usize>() -> Foo<N> { todo!() } // ERROR
// Can be fixed by wrapping in braces to force it to be interpreted as the `N`
// const parameter:
fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok
```

r[items.generics.const.variance]
Unlike type and lifetime parameters, const parameters can be declared without
being used inside of a parameterized item, with the exception of
Expand All @@ -207,26 +132,6 @@ struct Unconstrained;
impl<const N: usize> Unconstrained {}
```

r[items.generics.const.exhaustiveness]
When resolving a trait bound obligation, the exhaustiveness of all
implementations of const parameters is not considered when determining if the
bound is satisfied. For example, in the following, even though all possible
const values for the `bool` type are implemented, it is still an error that
the trait bound is not satisfied:

```rust,compile_fail
struct Foo<const B: bool>;
trait Bar {}
impl Bar for Foo<true> {}
impl Bar for Foo<false> {}

fn needs_bar(_: impl Bar) {}
fn generic<const B: bool>() {
let v = Foo::<B>;
needs_bar(v); // ERROR: trait bound `Foo<B>: Bar` is not satisfied
}
```

r[items.generics.where]
## Where clauses

Expand Down Expand Up @@ -284,16 +189,18 @@ struct Foo<#[my_flexible_clone(unbounded)] H> {
}
```

[^1]: There are limitations around how a const parameter can be used within a const argument ([const generics]).

[array repeat expression]: ../expressions/array-expr.md
[arrays]: ../types/array.md
[slices]: ../types/slice.md
[associated const]: associated-items.md#associated-constants
[associated type]: associated-items.md#associated-types
[attributes]: ../attributes.md
[block]: ../expressions/block-expr.md
[const contexts]: ../const_eval.md#const-context
[const expression]: ../const_eval.md#constant-expressions
[const item]: constant-items.md
[const generics]: ../const-generics.md
[enumerations]: enumerations.md
[functions]: functions.md
[function pointers]: ../types/function-pointer.md
Expand All @@ -303,7 +210,6 @@ struct Foo<#[my_flexible_clone(unbounded)] H> {
[implementations]: implementations.md
[item declarations]: ../statements.md#item-declarations
[item]: ../items.md
[literal]: ../expressions/literal-expr.md
[path]: ../paths.md
[path expression]: ../expressions/path-expr.md
[raw pointers]: ../types/pointer.md#raw-pointers-const-and-mut
Expand All @@ -315,4 +221,4 @@ struct Foo<#[my_flexible_clone(unbounded)] H> {
[type aliases]: type-aliases.md
[type]: ../types.md
[unions]: unions.md
[value namespace]: ../names/namespaces.md
[value namespace]: ../names/namespaces.md
3 changes: 2 additions & 1 deletion src/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,8 @@ r[patterns.const.exhaustive]

r[patterns.const.generic]
In particular, the value of `C` must be known at pattern-building time (which is pre-monomorphization).
This means that associated consts that involve generic parameters cannot be used as patterns.
This means that associated consts that involve generic parameters cannot be used as patterns and neither
can uses of const parameters.

r[patterns.const.translation]
After ensuring all conditions are met, the constant value is translated into a pattern, and now behaves exactly as-if that pattern had been written directly.
Expand Down
Loading