Types
Every variable, item, and value in a Rust program has a type. The type of a value defines the interpretation of the memory holding it and the operations that may be performed on the value.
Built-in types are tightly integrated into the language, in nontrivial ways that are not possible to emulate in user-defined types.
User-defined types have limited capabilities.
The list of types is:
- Primitive types:
- Sequence types:
- User-defined types:
- Function types:
- Pointer types:
- Trait types:
Type expressions
Syntax
Type →
TypeNoBounds
| ImplTraitType
| TraitObjectType
TypeNoBounds →
ParenthesizedType
| ImplTraitTypeOneBound
| TraitObjectTypeOneBound
| TypePath
| TupleType
| NeverType
| RawPointerType
| ReferenceType
| ArrayType
| SliceType
| InferredType
| QualifiedPathInType
| BareFunctionType
| MacroInvocation
A type expression as defined in the Type grammar rule above is the syntax for referring to a type. It may refer to:
- Type paths which can reference:
- Pointer types (reference, raw pointer, function pointer).
- The inferred type which asks the compiler to determine the type.
- Parentheses which are used for disambiguation.
- Trait types: Trait objects and impl trait.
- The never type.
- Macros which expand to a type expression.
Parenthesized types
Syntax
ParenthesizedType → ( Type )
In some situations the combination of types may be ambiguous. Use parentheses
around a type to avoid ambiguity. For example, the + operator for type
boundaries within a reference type is unclear where the
boundary applies, so the use of parentheses is required. Grammar rules that
require this disambiguation use the TypeNoBounds rule instead of
Type.
#![allow(unused)]
fn main() {
use std::any::Any;
type T<'a> = &'a (dyn Any + Send);
}
Recursive types
Nominal types — structs, enumerations, and unions — may be
recursive. That is, each enum variant or struct or union field may
refer, directly or indirectly, to the enclosing enum or struct type
itself.
Such recursion has restrictions:
- Recursive types must include a nominal type in the recursion (not mere type
aliases, or other structural types such as arrays or tuples). So
type Rec = &'static [Rec]is not allowed. - The size of a recursive type must be finite; in other words the recursive fields of the type must be pointer types.
An example of a recursive type and its use:
#![allow(unused)]
fn main() {
enum List<T> {
Nil,
Cons(T, Box<List<T>>)
}
let a: List<i32> = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil))));
}
Work in progress
Equality of types
Equality and subtyping of types is generally structural; if the outermost type constructors are the same, their corresponding generic arguments are pairwise compared. We say types with this equality behavior are rigid. The only exceptions from this rule are higher ranked types and alias types.
Aliases are compared by first normalizing them to a rigid type and then equating their type constructors and recursing into their generic arguments.
Function pointers and trait objects may be higher-ranked.
Subtyping is checked by instantiating the
forof the subtype with inference variables and theforof the supertype with placeholders before relating them as normal.Equality is checked by both instantiating the
forof one type with inference variables and theforof the other type with placeholders before equating them, and then doing the opposite.