Traits
A trait describes an abstract interface that types can implement. This interface consists of associated items, which come in three varieties:
All traits define an implicit type parameter Self
that refers to "the type
that is implementing this interface". Traits may also contain additional type
parameters. These type parameters, including Self
, may be constrained by
other traits and so forth as usual.
Traits are implemented for specific types through separate implementations.
Items associated with a trait do not need to be defined in the trait, but they may be. If the trait provides a definition, then this definition acts as a default for any implementation which does not override it. If it does not, then any implementation must provide a definition.
Trait bounds
Generic items may use traits as bounds on their type parameters.
Generic Traits
Type parameters can be specified for a trait to make it generic. These appear after the trait name, using the same syntax used in generic functions.
# #![allow(unused_variables)] #fn main() { trait Seq<T> { fn len(&self) -> u32; fn elt_at(&self, n: u32) -> T; fn iter<F>(&self, F) where F: Fn(T); } #}
Object Safety
Object safe traits can be the base trait of a trait object. A trait is object safe if it has the following qualities (defined in RFC 255):
- It must not require
Self: Sized
- All associated functions must either have a
where Self: Sized
bound, or- Not have any type parameters (although lifetime parameters are allowed), and
- Be a method that does not use
Self
except in the type of the receiver.
- It must not have any associated constants.
- All supertraits must also be object safe.
Supertraits
Supertraits are traits that are required to be implemented for a type to implement a specific trait. Furthermore, anywhere a [generic] or trait object is bounded by a trait, it has access to the associated items of its supertraits.
Supertraits are declared by trait bounds on the Self
type of a trait and
transitively the supertraits of the traits declared in those trait bounds. It is
an error for a trait to be its own supertrait.
The trait with a supertrait is called a subtrait of its supertrait.
The following is an example of declaring Shape
to be a supertrait of Circle
.
# #![allow(unused_variables)] #fn main() { trait Shape { fn area(&self) -> f64; } trait Circle : Shape { fn radius(&self) -> f64; } #}
And the following is the same example, except using where clauses.
# #![allow(unused_variables)] #fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64; } #}
This next example gives radius
a default implementation using the area
function from Shape
.
# #![allow(unused_variables)] #fn main() { # trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64 { // A = pi * r^2 // so algebraically, // r = sqrt(A / pi) (self.area() /std::f64::consts::PI).sqrt() } } #}
This next example calls a supertrait method on a generic parameter.
# #![allow(unused_variables)] #fn main() { # trait Shape { fn area(&self) -> f64; } # trait Circle : Shape { fn radius(&self) -> f64; } fn print_area_and_radius<C: Circle>(c: C) { // Here we call the area method from the supertrait `Shape` of `Circle`. println!("Area: {}", c.area()); println!("Radius: {}", c.radius()); } #}
Similarly, here is an example of calling supertrait methods on trait objects.
# #![allow(unused_variables)] #fn main() { # trait Shape { fn area(&self) -> f64; } # trait Circle : Shape { fn radius(&self) -> f64; } # struct UnitCircle; # impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } } # impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } } # let circle = UnitCircle; let circle = Box::new(circle) as Box<dyn Circle>; let nonsense = circle.radius() * circle.area(); #}