Introduction
In this lab, we explore the concept of associated types in Rust, which allows for improved readability of code by defining inner types locally within a trait as output types. This is achieved by using the type keyword within the trait definition. With associated types, functions that use the trait no longer need to explicitly express the types A and B, making the code more concise and flexible. We rewrite an example from a previous section using associated types to illustrate their usage in practice.
Note: If the lab does not specify a file name, you can use any file name you want. For example, you can use
main.rs, compile and run it withrustc main.rs && ./main.
Associated types
The use of "Associated types" improves the overall readability of code by moving inner types locally into a trait as output types. Syntax for the trait definition is as follows:
// `A` and `B` are defined in the trait via the `type` keyword.
// (Note: `type` in this context is different from `type` when used for
// aliases).
trait Contains {
type A;
type B;
// Updated syntax to refer to these new types generically.
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}
Note that functions that use the trait Contains are no longer required to express A or B at all:
// Without using associated types
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> { ... }
// Using associated types
fn difference<C: Contains>(container: &C) -> i32 { ... }
Let's rewrite the example from the previous section using associated types:
struct Container(i32, i32);
// A trait which checks if 2 items are stored inside of container.
// Also retrieves first or last value.
trait Contains {
// Define generic types here which methods will be able to utilize.
type A;
type B;
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
impl Contains for Container {
// Specify what types `A` and `B` are. If the `input` type
// is `Container(i32, i32)`, the `output` types are determined
// as `i32` and `i32`.
type A = i32;
type B = i32;
// `&Self::A` and `&Self::B` are also valid here.
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// Grab the first number.
fn first(&self) -> i32 { self.0 }
// Grab the last number.
fn last(&self) -> i32 { self.1 }
}
fn difference<C: Contains>(container: &C) -> i32 {
container.last() - container.first()
}
fn main() {
let number_1 = 3;
let number_2 = 10;
let container = Container(number_1, number_2);
println!("Does container contain {} and {}: {}",
&number_1, &number_2,
container.contains(&number_1, &number_2));
println!("First number: {}", container.first());
println!("Last number: {}", container.last());
println!("The difference is: {}", difference(&container));
}
Summary
Congratulations! You have completed the Associated Types lab. You can practice more labs in LabEx to improve your skills.