Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

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:

  • The inferred type which asks the compiler to determine the 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 for of the subtype with inference variables and the for of the supertype with placeholders before relating them as normal.

Equality is checked by both instantiating the for of one type with inference variables and the for of the other type with placeholders before equating them, and then doing the opposite.