Parameter Ty
/Const
/Region
s
When inside of generic items, types can be written that use in scope generic parameters, for example fn foo<'a, T>(_: &'a Vec<T>)
. In this specific case
the &'a Vec<T>
type would be represented internally as:
TyKind::Ref(
RegionKind::LateParam(DefId(foo), DefId(foo::'a), "'a"),
TyKind::Adt(Vec, &[TyKind::Param("T", 0)])
)
There are three separate ways we represent usages of generic parameters:
TyKind::Param
/ConstKind::Param
/RegionKind::EarlyParam
for early bound generic parameters (note: all type and const parameters are considered early bound, see the chapter on early vs late bound parameters for more information)TyKind::Bound
/ConstKind::Bound
/RegionKind::Bound
for references to parameters introduced via higher ranked bounds or higher ranked types i.e.for<'a> fn(&'a u32)
orfor<'a> T: Trait<'a>
. This will be discussed in the chapter onBinder
s.RegionKind::LateParam
for late bound lifetime parameters,LateParam
will be discussed in the chapter on instantiatingBinder
s.
This chapter will only cover TyKind::Param
ConstKind::Param
and RegionKind::EarlyParam
.
Ty/Const Parameters
As TyKind::Param
and ConstKind::Param
are implemented identically this section will only refer to TyKind::Param
for simplicity. However
you should keep in mind that everything here also is true of ConstKind::Param
Each TyKind::Param
contains two things: the name of the parameter and an index.
See the following concrete example of a usage of TyKind::Param
:
struct Foo<T>(Vec<T>);
The Vec<T>
type is represented as TyKind::Adt(Vec, &[GenericArgKind::Type(Param("T", 0))])
.
The name is somewhat self explanatory, it's the name of the type parameter. The index of the type parameter is an integer indicating its order in the list of generic parameters in scope (note: this includes parameters defined on items on outer scopes than the item the parameter is defined on). Consider the following examples:
struct Foo<A, B> {
// A would have index 0
// B would have index 1
.. // some fields
}
impl<X, Y> Foo<X, Y> {
fn method<Z>() {
// inside here, X, Y and Z are all in scope
// X has index 0
// Y has index 1
// Z has index 2
}
}
Concretely given the ty::Generics
for the item the parameter is defined on, if the index is 2
then starting from the root parent
, it will be the third parameter to be introduced. For example in the above example, Z
has index 2
and is the third generic parameter to be introduced, starting from the impl
block.
The index fully defines the Ty
and is the only part of TyKind::Param
that matters for reasoning about the code we are compiling.
Generally we do not care what the name is and only use the index is included for diagnostics and debug logs as otherwise it would be
incredibly difficult to understand the output, i.e. Vec<Param(0)>: Sized
vs Vec<T>: Sized
. In debug output, parameter types are
often printed out as {name}/#{index}
, for example in the function foo
if we were to debug print Vec<T>
it would be written as Vec<T/#0>
.
An alternative representation would be to only have the name, however using an index is more efficient as it means we can index into GenericArgs
when instantiating generic parameters with some arguments. We would otherwise have to store GenericArgs
as a HashMap<Symbol, GenericArg>
and do a hashmap lookup everytime we used a generic item.
In theory an index would also allow for having multiple distinct parameters that use the same name, e.g.
impl<A> Foo<A> { fn bar<A>() { .. } }
.
The rules against shadowing make this difficult but those language rules could change in the future.
Lifetime parameters
In contrast to Ty
/Const
's Param
singular Param
variant, lifetimes have two variants for representing region parameters: RegionKind::EarlyParam
and RegionKind::LateParam
. The reason for this is due to function's distinguishing between early and late bound parameters which is discussed in an earlier chapter (see link).
RegionKind::EarlyParam
is structured identically to Ty/Const
's Param
variant, it is simply a u32
index and a Symbol
. For lifetime parameters defined on non-function items we always use ReEarlyParam
. For functions we use ReEarlyParam
for any early bound parameters and ReLateParam
for any late bound parameters. Note that just like Ty
and Const
params we often debug format them as 'SYMBOL/#INDEX
, see for example:
// This function would have its signature represented as:
//
// ```
// fn(
// T/#2,
// Ref('a/#0, Ref(ReLateParam(...), u32))
// ) -> Ref(ReLateParam(...), u32)
// ```
fn foo<'a, 'b, T: 'a>(one: T, two: &'a &'b u32) -> &'b u32 {
...
}
RegionKind::LateParam
will be discussed more in the chapter on instantiating binders.