The ty
module defines how the Rust compiler represents types
internally. It also defines the typing context (tcx
or TyCtxt
),
which is the central data structure in the compiler.
The tcx
("typing context") is the central data structure in the
compiler. It is the context that you use to perform all manner of
queries. The struct TyCtxt
defines a reference to this shared context:
tcx: TyCtxt<'tcx>
// ----
// |
// arena lifetime
As you can see, the TyCtxt
type takes a lifetime parameter.
During Rust compilation, we allocate most of our memory in
arenas, which are basically pools of memory that get freed all at
once. When you see a reference with a lifetime like 'tcx
,
you know that it refers to arena-allocated data (or data that lives as
long as the arenas, anyhow).
Rust types are represented using the Ty<'tcx>
defined in the ty
module (not to be confused with the Ty
struct from the HIR). This
is in fact a simple type alias for a reference with 'tcx
lifetime:
pub type Ty<'tcx> = &'tcx TyS<'tcx>;
You can basically ignore the TyS
struct – you will basically never
access it explicitly. We always pass it by reference using the
Ty<'tcx>
alias – the only exception I think is to define inherent
methods on types. Instances of TyS
are only ever allocated in one of
the rustc arenas (never e.g. on the stack).
One common operation on types is to match and see what kinds of
types they are. This is done by doing match ty.sty
, sort of like this:
fn test_type<'tcx>(ty: Ty<'tcx>) {
match ty.sty {
ty::TyArray(elem_ty, len) => { ... }
...
}
}
The sty
field (the origin of this name is unclear to me; perhaps
structural type?) is of type TyKind<'tcx>
, which is an enum
defining all of the different kinds of types in the compiler.
N.B. inspecting the
sty
field on types during type inference can be risky, as there may be inference variables and other things to consider, or sometimes types are not yet known that will become known later.).
To allocate a new type, you can use the various mk_
methods defined
on the tcx
. These have names that correspond mostly to the various kinds
of type variants. For example:
let array_ty = tcx.mk_array(elem_ty, len * 2);
These methods all return a Ty<'tcx>
– note that the lifetime you
get back is the lifetime of the innermost arena that this tcx
has
access to. In fact, types are always canonicalized and interned (so we
never allocate exactly the same type twice) and are always allocated
in the outermost arena where they can be (so, if they do not contain
any inference variables or other "temporary" types, they will be
allocated in the global arena). However, the lifetime 'tcx
is always
a safe approximation, so that is what you get back.
NB. Because types are interned, it is possible to compare them for equality efficiently using
==
– however, this is almost never what you want to do unless you happen to be hashing and looking for duplicates. This is because often in Rust there are multiple ways to represent the same type, particularly once inference is involved. If you are going to be testing for type equality, you probably need to start looking into the inference code to do it right.
You can also find various common types in the tcx
itself by accessing
tcx.types.bool
, tcx.types.char
, etc (see CommonTypes
for more).
In addition to types, there are a number of other arena-allocated data structures that you can allocate, and which are found in this module. Here are a few examples:
Substs
, allocated withmk_substs
– this will intern a slice of types, often used to specify the values to be substituted for generics (e.g.HashMap<i32, u32>
would be represented as a slice&'tcx [tcx.types.i32, tcx.types.u32]
).TraitRef
, typically passed by value – a trait reference consists of a reference to a trait along with its various type parameters (includingSelf
), likei32: Display
(here, the def-id would reference theDisplay
trait, and the substs would containi32
).Predicate
defines something the trait system has to prove (seetraits
module).
Although there is no hard and fast rule, the ty
module tends to be used like
so:
use ty::{self, Ty, TyCtxt};
In particular, since they are so common, the Ty
and TyCtxt
types
are imported directly. Other types are often referenced with an
explicit ty::
prefix (e.g. ty::TraitRef<'tcx>
). But some modules
choose to import a larger or smaller set of names explicitly.