A type is a classification of values. Values of the same type have a similar meaning and purpose, and support the same operations. Values of different types can have the same memory representation – for example, both u8 and bool can be represented by a zero, but because they are different types, the zero has a different meaning.

In Rust, types are enforced at compile time to prevent logic errors. However, since memory safety invariants are encoded in the type system, it also prevents memory safety bugs.

Types in Rust can be divided into:

Types can be generic. Generic types are sometimes called type constructors. For example, Box<T> is generic over T. By substituting T with a type, new types such as Box<bool> or Box<String> can be created.

Traits aren't types, but they categorize types with certain properties or functionality.

ExampleEdit

fn function(x: i32) -> u32 {
// type of x ~~^^^     ^^^ return type

    // type conversion:
    let y = x as u32;
    
    // the type of 5 is inferred:
    y + 5
}

Implementing a typeEdit

After declaring a type, impl blocks can be used to add functionality to it. These are called inherent impls, and functions within them are called inherent functions (or methods):
// declare a struct type:
pub struct Foo;

// an inherent impl:
impl Foo {
    // an inherent function:
    pub fn new() -> Self {
        Foo
    }
}
Types can have multiple inherent impl blocks (except primitive types). Unlike trait functions, inherent functions can have a visibility modifier. Inherent impls must always be in the same crate as the type they implement.

Nominal and structural typesEdit

Types can be divided into nominal types (types with a name) and structural types. Only nominal types can have inherent impl blocks.

Nominal types include:

All other types are structural:

Type aliasesEdit

Type aliases don't create a new type, just a new name to refer to a type. Different aliases that refer to the same type can be used interchangeably. Type aliases can be generic:
struct Foo<T>(T);

type MaybeFoo<T> = Result<Foo<T>, ()>;

// with a generic error type that defaults to String:
type FooResult<T, Err = String> = Result<Foo<T>, Err>;

Type inferenceEdit

Types of parameters and the return type of functions need to be specified explicitly. In other places, types can often be automatically inferred.

The compiler does this by giving values whose type isn't known an {unknown} type placeholder. Then the surrounding context is used to replace the type placeholders with actual types.

ExampleEdit

Let's look at an example where a lot of type inference takes place, and see how the compiler approaches it:
fn foo(iter: impl IntoIterator<Item = i32>) {
    // what is x?
    let x: Vec<_> = iter
        .into_iter()
        .map(|n| n * 2)
        .collect();
}
We don't know the type of iter, but we know that it implements the IntoIterator  trait. IntoIterator::into_iter() returns Self::IntoIter, which implements Iterator<Item = Self::Item> .

Since the Item is i32, this tells the compiler that n in line 5 is an i32. Since i32 implements Mul<Rhs = Self> , Rust also infers the type i32 for the 2 literal. The return type is Self::Output , which also happens to be i32.

Iterator::map()  accepts a closure and returns a type which implements Iterator. The Item of the iterator is the return type of the closure (i32).

Iterator::collect()  returns a generic type B that implements FromIterator<Self::Item> . The compiler knows that the return type must be a Vec , so it looks for implementations of FromIterator<Self::Item> for Vec, and it finds impl<T> FromIterator<T> for Vec<T>. This means that the type in the Vec<_> is the same type as Self::Item, so Rust correctly infers the type Vec<i32> for x.