Skip to content
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

Add more details about const generics. #921

Merged
merged 3 commits into from
Jan 11, 2021
Merged
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
193 changes: 177 additions & 16 deletions src/items/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
> _ConstParam_:\
>    `const` [IDENTIFIER] `:` [_Type_]

Functions, type aliases, structs, enumerations, unions, traits, and
implementations may be *parameterized* by types, constants, and lifetimes. These
[Functions], [type aliases], [structs], [enumerations], [unions], [traits], and
[implementations] may be *parameterized* by types, constants, and lifetimes. These
parameters are listed in angle <span class="parenthetical">brackets (`<...>`)</span>,
usually immediately after the name of the item and before its definition. For
implementations, which don't have a name, they come directly after `impl`.
Expand All @@ -33,22 +33,147 @@ struct Ref<'a, T> where T: 'a { r: &'a T }
struct InnerArray<T, const N: usize>([T; N]);
```

Generic parameters are in scope within the item definition where they are
declared. They are not in scope for items declared within the body of a
function as described in [item declarations].

[References], [raw pointers], [arrays], [slices][arrays], [tuples], and
Copy link
Contributor

Choose a reason for hiding this comment

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

This was here before, but I have no clue what this sentence is actually stating. Path syntax isn't mentioned at all up to this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Introduced in #258. If I had to guess, it is maybe trying to say is that the syntax for specifying arguments to those types does not use the same syntax that the other items (structs, fns, etc.) use. That is, instead of IDENT<…> they each have their own unique syntax for specifying arguments.

I agree, it seems a bit confusing. Any suggestions? Maybe @matthewjasper could clarify?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I let my brain think about it overnight. I'm betting this statement was originally in paths.html where it makes sense and was moved here as part of a split out. We should probably bring most of GenericArgument into generics.html and then rename GenericArguments to GenericPathArguments or something. I'll open an issue for that one.

[function pointers] have lifetime or type parameters as well, but are not
referred to with path syntax.

### Const generics

*Const generic parameters* allow items to be generic over constant values. The
const identifier introduces a name for the constant parameter, and all
instances of the item must be instantiated with a value of the given type.

<!-- TODO: update above to say "introduces a name in the [value namespace]"
once namespaces are added. -->

The only allowed types of const parameters are `u8`, `u16`, `u32`, `u64`, `u128`, `usize`
`i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `char` and `bool`.

Const parameters may only be be used as standalone arguments inside
of [types] and [repeat expressions] but may be freely used elsewhere:
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.

```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.
let x: [i32; N];
// Used as an expression.
println!("{}", N * 2);
}

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

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

trait Trait {
type Output;
}

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

```rust,compile_fail
// Examples where const generic parameters cannot be used.
fn foo<const N: usize>() {
// Cannot use in item definitions within a function body.
const BAD_CONST: [usize; N] = [1; N];
static BAD_STATIC: [usize; N] = [1; N];
fn inner(bad_arg: [usize; N]) {
let bad_value = N * 2;
}
type BadAlias = [usize; N];
struct BadStruct([usize; N]);
}
```

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
// ok: standalone argument
fn foo<const N: usize>() -> [u8; N] { todo!() }
// Examples where const parameters may not be used.

// ERROR: generic const operation
fn bar<const N: usize>() -> [u8; N + 1] { todo!() }
// 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}]
}
```

Unlike type and lifetime parameters, const parameters of types can be used without
being mentioned inside of a parameterized type:
A const argument in a [path] specifies the const value to use for that item.
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 }>();
}
```

When there is ambiguity if a generic argument could be resolved as either a
Havvy marked this conversation as resolved.
Show resolved Hide resolved
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 interprted as the `N`
// const parameter:
fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok
```

Unlike type and lifetime parameters, const parameters can be declared without
being used inside of a parameterized item, with the exception of
implementations as described in [generic implementations]:

```rust,compile_fail
// ok
Expand All @@ -58,11 +183,29 @@ enum Bar<const M: usize> { A, B }
// ERROR: unused parameter
struct Baz<T>;
struct Biz<'a>;
struct Unconstrained;
impl<const N: usize> Unconstrained {}
```

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
}
```

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

## Where clauses

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

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

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

[array repeat expression]: ../expressions/array-expr.md
[arrays]: ../types/array.md
[associated const]: associated-items.md#associated-constants
[associated type]: associated-items.md#associated-types
[block]: ../expressions/block-expr.md
[const contexts]: ../const_eval.md#const-context
[const expression]: ../const_eval.md#constant-expressions
[const item]: constant-items.md
[enumerations]: enumerations.md
[functions]: functions.md
[function pointers]: ../types/function-pointer.md
[generic implementations]: implementations.md#generic-implementations
[higher-ranked lifetimes]: ../trait-bounds.md#higher-ranked-trait-bounds
[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
[references]: ../types/pointer.md#shared-references-
[repeat expressions]: ../expressions/array-expr.md
[`Clone`]: ../special-types-and-traits.md#clone
[`Copy`]: ../special-types-and-traits.md#copy
[`Sized`]: ../special-types-and-traits.md#sized
[structs]: structs.md
[tuples]: ../types/tuple.md
[trait object]: ../types/trait-object.md
[types]: ../types.md
[traits]: traits.md
[type aliases]: type-aliases.md
[type]: ../types.md
[unions]: unions.md
[attributes]: ../attributes.md
12 changes: 7 additions & 5 deletions src/items/implementations.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,11 @@ is considered local.

## Generic Implementations

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

```rust
# trait Seq<T> { fn dummy(&self, _: T) { } }
Expand Down Expand Up @@ -219,6 +219,7 @@ attributes].
[trait]: traits.md
[associated functions]: associated-items.md#associated-functions-and-methods
[associated constants]: associated-items.md#associated-constants
[associated type]: associated-items.md#associated-types
[attributes]: ../attributes.md
[`cfg`]: ../conditional-compilation.md
[`deprecated`]: ../attributes/diagnostics.md#the-deprecated-attribute
Expand All @@ -230,3 +231,4 @@ attributes].
[local type]: ../glossary.md#local-type
[fundamental types]: ../glossary.md#fundamental-type-constructors
[uncovered type]: ../glossary.md#uncovered-type
[generic parameters]: generics.md