Generics & Traits

By using Generics and Traits we can leverage their combined power to write efficient, reusable, and type-safe code.

Generics

  • Generics used to create definitions for items like function signatures or structs, which we can then use with many different concrete data types.

  • So instead of writing separate implementations for each type, you define a generic type that can be substituted with concrete types when the code is used.

  • Represented by angle brackets (<>) and often denoted by T, U, or other descriptive names.

  • Generics in Rust are resolved at compile time, ensuring that invalid types cannot be used.

  • Rust generates optimized code for each concrete type used with a generic at compile time, avoiding runtime overhead.

Generic Functions

  • Functions can use generics to operate on multiple types.

    fn hello<T: std::fmt::Display>(name: T) {
        println!("hello {}!", name);
    }
    
    fn main() {
        hello(777);
        hello("World");
    }

Generic Structs

  • Structs can hold values of any type using generics.

Generic Enums

  • Enums can also use generics, as seen with Option or Result.

Traits

  • A trait is similar to an interface in other programming languages.

  • It defines a set of methods that a type must implement, allowing you to specify what functionality a type provides without dictating how it provides it.

  • Implementing a trait on a type is similar to implementing regular methods.

  • The difference is that after impl, we put the trait name we want to implement, then use the for keyword, and then specify the name of the type we want to implement the trait for.

  • Traits can provide default method implementations that types can override.

  • The impl Trait syntax works for straightforward cases but is actually syntax sugar for a longer form known as a trait bound.

  • We can also specify more than one trait bound using + syntax.

Defining a Trait

  • A trait defines methods that other types can implement.

Implementing a Trait

  • Implementing a trait similar like implementing method by put the trait name after impl and then specify the type after for.

Default Implementation

  • Traits can provide default method implementations that types can override.

Traits as Parameters

  • Traits can be used as parameters using impl Trait syntax.

  • The impl Trait syntax works for straightforward cases but is actually syntax sugar for a longer form known as a trait bound; it looks like this:

  • We also can have multiple parameters that implement a trait.

  • For example this function have two parameters that implement Summary:

  • We can also do it using generics.

  • But in the example below the generic type T specified as the type of the item1 and item2 parameters constrains the function such that the concrete type of the value passed as an argument for item1 and item2 must be the same.

  • We can also use the impl Trait syntax in the return position to return a value of some type that implements a trait.

Specifying Multiple Trait Bounds

  • We can also specify more than one trait bound using + syntax.

  • The + syntax is also valid with trait bounds on generic types.

Generic Traits

  • Traits can also be generic, allowing them to operate over a range of types.

Extending Existing Traits

  • We can extend existing traits by combining them or adding additional behavior.

References

Last updated