Traits, methods


The receiver.method(...) syntax is used to call a "method": a function whose first parameter is a variant on self; often &self or &mut self. (self is a keyword; you can't choose your own name for it.)

Methods are defined in a block impl StructName { } (and can also be part of traits).

There is no inheritance. Some of the same effects can be achieved with traits, particularly default trait methods, and/or macro crates like delegate or ambassador.

It follows from the ownership model that a method defined fn foo(self,...) consumes its argument (unless it's Copy) so that it can no longer be used. This can be used to good effect in typestate-like APIs.


Rust leans very heavily on its trait system.

Rust Traits are very like Haskell Typeclasses, or C++ Concepts.

A trait defines a set of interfaces (usually, methods), including possibly default implementations. A trait must be explicitly implemented for a type with impl Trait for Type { ... }, giving definitions of all the items (perhaps except items with defaults).

Trait items (eg methods) and "inherent" items (belonging to a particular type) with the same name are different items. In this case when implementing a trait it can be necessary to explicitly write out the implementation of a trait method in terms of the inherent method. However, it is often idiomatic to provide functionality only through trait implementations.

When a trait has (roughly speaking) only methods, pointers to objects which implement the trait can be made into pointers to type-erased trait objects dyn Trait. These "fat pointers" have a vtable as well as the actual object pointer. Trait objects are often seen in the form Box<dyn Trait>. Ability of a trait to be used this way is called "object safety" (confusingly; it's not related to safety). The rules are a bit complicated but often a trait can be made object-safe by adding where Self: Sized to troublesome methods.

Rust has a strict trait coherence system. There can be only one implementation of a trait for any one concrete type, in the whole program. (Source-code level specialisation is not available in Stable Rust.) To ensure this, it is forbidden (in summary) to implement a foreign trait on a foreign type (where "foreign" means outside your crate, not outside your module).

Iterators: Iterator, IntoIterator, FromIterator

The Iterator and IntoIterator traits are very important in idiomatic (and performant) Rust.

Most collections and many other key types (eg, Option) implement Iterator and/or IntoIterator, so that they can be iterated over; this is how for x in y loops work: y must impl IntoIterator.

The standard library provides a large set of combinator methods on Iterator, for mapping, folding, filtering, and so on. These typically take closures as arguments. See also the excellent itertools crate.

Idiomatic coding style for iteration in Rust involves chaining iterator combinators. Effectively, Rust contains an iterator monad sublanguage with a funky syntax. (More in this essay: The problem of effects in Rust by withoutboats.)

The .collect() method in Iterator reassembles the result of an iteration back into a collection (or something which could be a collection if you squint; note for example the FromIterator impl for Result). Often one has to write the type of the desired result, perhaps like this:

let processed = things
    .filter_map(|t| ...)
    .map(|t| ...?; ...; Ok(u))

collect is more idiomatic than open-coding additions to a mutable collection variable: use of iterators is often faster than a for loop, and aggressively-Rustic style tries to minimise the use of mut variables.

Existential types

Rust has some very limited support for existential types. This is written impl Trait, and means "there is some concrete type here which implements this trait but I'm not telling you what it is". This is commonly used for functions returning iterators, and for futures (see Async Rust).

Currently this is only allowed in function signatures, typically as the return type. e.g.

   fn get_strings() -> Result<impl Iterator<Item=String>, io::Error>;

It is not currently possible (on stable) to make an alias for the existential type, so you still can't name it properly, put it into variables, etc. This can be inconvenient and work is ongoing. In the meantime, the usual workaround is to use Box<dyn Trait> instead of impl Trait.

Closures and the fn pointer type

Each closure is a value of a unique opaque unnameable type implementing one or more of the special closure traits Fn, FnMut and FnOnce.

The different traits are because closures can borrow or own variables. If the closure modifies closed-over variables, it is FnMut; if it consumes them, it is FnOnce.

Each closure has its own separate unnameable type, so closures can only be used with polymorphism (whether monomorphised <F: Fn()>, or type-erased &dyn Fn()).

Closures borrow their captures during their whole existence, not just while they're running. This can impede their use to avoid repetition.

dyn closures

An &dyn Fn closure pointer is a fat pointer: closed-over data, and code pointer.

A dyn closure trait object cannot be passed by value because it's unsized. This can make FnOnce closures awkward. Use monomorphisation, Box<dyn FnOnce> or somehow make the closure be FnMut.

Monomorphised closures f: F where F: Fn()

Monomorphisation of generic closure arguments specialises the generic function taking the closure to one which calls the specific closure.

The concrete representation of a particular closure type is an unnameable struct containing the closed-over variables.

The code is known at compile time -- it is identified by the precise (unnameable) closure type -- so a pointer to it not part of the closure representation at runtime. Likewise, the nature of the closed-over variables, and their uses, are known at compile-time.

The monomorphised caller of a closure calls it directly (statically known branch target). The closure can even be inlined and its code and closed-over variables intermixed with its caller's, and the outer caller's, to produce more optimal code.

fn pointers

There is also a pointer type fn(args..) -> T but this is just a code pointer, so only actual functions, and closures with no captured variables, count.

Some other key traits

Deref and method resolution

The magic traits Deref and DerefMut allow a type to "dereference to" another type. This is typically used for types like Arc, Box and MutexGuard which are "smart" pointers to some other type (ie, somehow a pointer, but with additional behaviour).

During method resolution, Deref is applied repeatedly to try to find a type with the appropriately-named method. The signature of the method is not considered during resolution, so there is no signature-based method overloading/despatch.

If it is necessary to specify a particular method, Type::method(receiver,...) or Trait::method can be used, or even <T as Trait>::method.

This is also required for associated functions (whether inherent or in traits) which are not methods (do not take a self parameter). Idiomatically this includes constructors like T::new() and can also include other functions that the struct's author has decided ought not to be methods. For example Arc::downgrade is not a method to avoid interfering with any downgrade method on T.

Deref effectively imports the dereference target type's methods into the method namespace of the dereferencable object. This could be used for a kind of method inheritance, but this is considered bad style (and it wouldn't work for multiple inheritance, since there can be only one deref target).

Auto-dereferencing also occurs when a reference is assigned (to a variable, or as part of parameter passing): if the type does not match, an attempt is made to see if dereferencing (perhaps multiple times) will help.

The Deref[Mut] implementation can be invoked explicitly with the * operator. Sometimes when this is necessary, one wants a reference again, so constructions like &mut **x are not unheard-of.