Skip to content

Commit 3e93fea

Browse files
authored
Merge pull request #921 from ehuss/const-generics-ext
Add more details about const generics.
2 parents 6dc5c2f + 7fd47ef commit 3e93fea

File tree

2 files changed

+184
-21
lines changed

2 files changed

+184
-21
lines changed

src/items/generics.md

+177-16
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
> _ConstParam_:\
1818
>    `const` [IDENTIFIER] `:` [_Type_]
1919
20-
Functions, type aliases, structs, enumerations, unions, traits, and
21-
implementations may be *parameterized* by types, constants, and lifetimes. These
20+
[Functions], [type aliases], [structs], [enumerations], [unions], [traits], and
21+
[implementations] may be *parameterized* by types, constants, and lifetimes. These
2222
parameters are listed in angle <span class="parenthetical">brackets (`<...>`)</span>,
2323
usually immediately after the name of the item and before its definition. For
2424
implementations, which don't have a name, they come directly after `impl`.
@@ -33,22 +33,147 @@ struct Ref<'a, T> where T: 'a { r: &'a T }
3333
struct InnerArray<T, const N: usize>([T; N]);
3434
```
3535

36+
Generic parameters are in scope within the item definition where they are
37+
declared. They are not in scope for items declared within the body of a
38+
function as described in [item declarations].
39+
40+
[References], [raw pointers], [arrays], [slices][arrays], [tuples], and
41+
[function pointers] have lifetime or type parameters as well, but are not
42+
referred to with path syntax.
43+
44+
### Const generics
45+
46+
*Const generic parameters* allow items to be generic over constant values. The
47+
const identifier introduces a name for the constant parameter, and all
48+
instances of the item must be instantiated with a value of the given type.
49+
50+
<!-- TODO: update above to say "introduces a name in the [value namespace]"
51+
once namespaces are added. -->
52+
3653
The only allowed types of const parameters are `u8`, `u16`, `u32`, `u64`, `u128`, `usize`
3754
`i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `char` and `bool`.
3855

39-
Const parameters may only be be used as standalone arguments inside
40-
of [types] and [repeat expressions] but may be freely used elsewhere:
56+
Const parameters can be used anywhere a [const item] can be used, with the
57+
exception that when used in a [type] or [array repeat expression], it must be
58+
standalone (as described below). That is, they are allowed in the following
59+
places:
60+
61+
1. As an applied const to any type which forms a part of the signature of the
62+
item in question.
63+
2. As part of a const expression used to define an [associated const], or as a
64+
parameter to an [associated type].
65+
3. As a value in any runtime expression in the body of any functions in the
66+
item.
67+
4. As a parameter to any type used in the body of any functions in the item.
68+
5. As a part of the type of any fields in the item.
69+
70+
```rust
71+
// Examples where const generic parameters can be used.
72+
73+
// Used in the signature of the item itself.
74+
fn foo<const N: usize>(arr: [i32; N]) {
75+
// Used as a type within a function body.
76+
let x: [i32; N];
77+
// Used as an expression.
78+
println!("{}", N * 2);
79+
}
80+
81+
// Used as a field of a struct.
82+
struct Foo<const N: usize>([i32; N]);
83+
84+
impl<const N: usize> Foo<N> {
85+
// Used as an associated constant.
86+
const CONST: usize = N * 4;
87+
}
88+
89+
trait Trait {
90+
type Output;
91+
}
92+
93+
impl<const N: usize> Trait for Foo<N> {
94+
// Used as an associated type.
95+
type Output = [i32; N];
96+
}
97+
```
98+
99+
```rust,compile_fail
100+
// Examples where const generic parameters cannot be used.
101+
fn foo<const N: usize>() {
102+
// Cannot use in item definitions within a function body.
103+
const BAD_CONST: [usize; N] = [1; N];
104+
static BAD_STATIC: [usize; N] = [1; N];
105+
fn inner(bad_arg: [usize; N]) {
106+
let bad_value = N * 2;
107+
}
108+
type BadAlias = [usize; N];
109+
struct BadStruct([usize; N]);
110+
}
111+
```
112+
113+
As a further restriction, const parameters may only appear as a standalone
114+
argument inside of a [type] or [array repeat expression]. In those contexts,
115+
they may only be used as a single segment [path expression], possibly inside a
116+
[block] (such as `N` or `{N}`). That is, they cannot be combined with other
117+
expressions.
41118

42119
```rust,compile_fail
43-
// ok: standalone argument
44-
fn foo<const N: usize>() -> [u8; N] { todo!() }
120+
// Examples where const parameters may not be used.
45121
46-
// ERROR: generic const operation
47-
fn bar<const N: usize>() -> [u8; N + 1] { todo!() }
122+
// Not allowed to combine in other expressions in types, such as the
123+
// arithmetic expression in the return type here.
124+
fn bad_function<const N: usize>() -> [u8; {N + 1}] {
125+
// Similarly not allowed for array repeat expressions.
126+
[1; {N + 1}]
127+
}
48128
```
49129

50-
Unlike type and lifetime parameters, const parameters of types can be used without
51-
being mentioned inside of a parameterized type:
130+
A const argument in a [path] specifies the const value to use for that item.
131+
The argument must be a [const expression] of the type ascribed to the const
132+
parameter. The const expression must be a [block expression][block]
133+
(surrounded with braces) unless it is a single path segment (an [IDENTIFIER])
134+
or a [literal] (with a possibly leading `-` token).
135+
136+
> **Note**: This syntactic restriction is necessary to avoid requiring
137+
> infinite lookahead when parsing an expression inside of a type.
138+
139+
```rust
140+
fn double<const N: i32>() {
141+
println!("doubled: {}", N * 2);
142+
}
143+
144+
const SOME_CONST: i32 = 12;
145+
146+
fn example() {
147+
// Example usage of a const argument.
148+
double::<9>();
149+
double::<-123>();
150+
double::<{7 + 8}>();
151+
double::<SOME_CONST>();
152+
double::<{ SOME_CONST + 5 }>();
153+
}
154+
```
155+
156+
When there is ambiguity if a generic argument could be resolved as either a
157+
type or const argument, it is always resolved as a type. Placing the argument
158+
in a block expression can force it to be interpreted as a const argument.
159+
160+
<!-- TODO: Rewrite the paragraph above to be in terms of namespaces, once
161+
namespaces are introduced, and it is clear which namespace each parameter
162+
lives in. -->
163+
164+
```rust,compile_fail
165+
type N = u32;
166+
struct Foo<const N: usize>;
167+
// The following is an error, because `N` is interpreted as the type alias `N`.
168+
fn foo<const N: usize>() -> Foo<N> { todo!() } // ERROR
169+
// Can be fixed by wrapping in braces to force it to be interprted as the `N`
170+
// const parameter:
171+
fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok
172+
```
173+
174+
Unlike type and lifetime parameters, const parameters can be declared without
175+
being used inside of a parameterized item, with the exception of
176+
implementations as described in [generic implementations]:
52177

53178
```rust,compile_fail
54179
// ok
@@ -58,11 +183,29 @@ enum Bar<const M: usize> { A, B }
58183
// ERROR: unused parameter
59184
struct Baz<T>;
60185
struct Biz<'a>;
186+
struct Unconstrained;
187+
impl<const N: usize> Unconstrained {}
188+
```
189+
190+
When resolving a trait bound obligation, the exhaustiveness of all
191+
implementations of const parameters is not considered when determining if the
192+
bound is satisfied. For example, in the following, even though all possible
193+
const values for the `bool` type are implemented, it is still an error that
194+
the trait bound is not satisfied:
195+
196+
```rust,compile_fail
197+
struct Foo<const B: bool>;
198+
trait Bar {}
199+
impl Bar for Foo<true> {}
200+
impl Bar for Foo<false> {}
201+
202+
fn needs_bar(_: impl Bar) {}
203+
fn generic<const B: bool>() {
204+
let v = Foo::<B>;
205+
needs_bar(v); // ERROR: trait bound `Foo<B>: Bar` is not satisfied
206+
}
61207
```
62208

63-
[References], [raw pointers], [arrays], [slices][arrays], [tuples], and
64-
[function pointers] have lifetime or type parameters as well, but are not
65-
referred to with path syntax.
66209

67210
## Where clauses
68211

@@ -90,7 +233,7 @@ parameters.
90233
The `for` keyword can be used to introduce [higher-ranked lifetimes]. It only
91234
allows [_LifetimeParam_] parameters.
92235

93-
Bounds that don't use the item's parameters or higher-ranked lifetimes are
236+
Bounds that don't use the item's parameters or [higher-ranked lifetimes] are
94237
checked when the item is defined. It is an error for such a bound to be false.
95238

96239
[`Copy`], [`Clone`], and [`Sized`] bounds are also checked for certain generic
@@ -141,17 +284,35 @@ struct Foo<#[my_flexible_clone(unbounded)] H> {
141284
[_Type_]: ../types.md#type-expressions
142285
[_TypeParamBounds_]: ../trait-bounds.md
143286

287+
[array repeat expression]: ../expressions/array-expr.md
144288
[arrays]: ../types/array.md
289+
[associated const]: associated-items.md#associated-constants
290+
[associated type]: associated-items.md#associated-types
291+
[block]: ../expressions/block-expr.md
145292
[const contexts]: ../const_eval.md#const-context
293+
[const expression]: ../const_eval.md#constant-expressions
294+
[const item]: constant-items.md
295+
[enumerations]: enumerations.md
296+
[functions]: functions.md
146297
[function pointers]: ../types/function-pointer.md
298+
[generic implementations]: implementations.md#generic-implementations
147299
[higher-ranked lifetimes]: ../trait-bounds.md#higher-ranked-trait-bounds
300+
[implementations]: implementations.md
301+
[item declarations]: ../statements.md#item-declarations
302+
[item]: ../items.md
303+
[literal]: ../expressions/literal-expr.md
304+
[path]: ../paths.md
305+
[path expression]: ../expressions/path-expr.md
148306
[raw pointers]: ../types/pointer.md#raw-pointers-const-and-mut
149307
[references]: ../types/pointer.md#shared-references-
150-
[repeat expressions]: ../expressions/array-expr.md
151308
[`Clone`]: ../special-types-and-traits.md#clone
152309
[`Copy`]: ../special-types-and-traits.md#copy
153310
[`Sized`]: ../special-types-and-traits.md#sized
311+
[structs]: structs.md
154312
[tuples]: ../types/tuple.md
155313
[trait object]: ../types/trait-object.md
156-
[types]: ../types.md
314+
[traits]: traits.md
315+
[type aliases]: type-aliases.md
316+
[type]: ../types.md
317+
[unions]: unions.md
157318
[attributes]: ../attributes.md

src/items/implementations.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,11 @@ is considered local.
180180

181181
## Generic Implementations
182182

183-
An implementation can take type and lifetime parameters, which can be used in
184-
the rest of the implementation. Type parameters declared for an implementation
185-
must be used at least once in either the trait or the implementing type of an
186-
implementation. Implementation parameters are written directly after the `impl`
187-
keyword.
183+
An implementation can take [generic parameters] which are written directly
184+
after the `impl` keyword. The parameters can be used in the rest of the
185+
implementation. Type and const parameters must be used at least once in either
186+
the trait or the implementing type of an implementation. Lifetime parameters
187+
do not need to be used unless they appear in an [associated type].
188188

189189
```rust
190190
# trait Seq<T> { fn dummy(&self, _: T) { } }
@@ -219,6 +219,7 @@ attributes].
219219
[trait]: traits.md
220220
[associated functions]: associated-items.md#associated-functions-and-methods
221221
[associated constants]: associated-items.md#associated-constants
222+
[associated type]: associated-items.md#associated-types
222223
[attributes]: ../attributes.md
223224
[`cfg`]: ../conditional-compilation.md
224225
[`deprecated`]: ../attributes/diagnostics.md#the-deprecated-attribute
@@ -230,3 +231,4 @@ attributes].
230231
[local type]: ../glossary.md#local-type
231232
[fundamental types]: ../glossary.md#fundamental-type-constructors
232233
[uncovered type]: ../glossary.md#uncovered-type
234+
[generic parameters]: generics.md

0 commit comments

Comments
 (0)