Traits

A trait is a collection of methods.

Data types can implement traits. To do so, the methods making up the trait are defined for the data type. For example, the String data type implements the From<&str> trait. This allows a user to write String::from("hello").

In this way, traits are somewhat similar to Java interfaces and C++ abstract classes.

Some additional common Rust traits include:

  • Clone (the clone method)
  • Display (which allows formatted display via {})
  • Debug (which allows formatted display via {:?})

Because traits indicate shared behavior between data types, they are useful when writing generics.

Further information

Rustlings

traits1: Time to implement some traits!

// traits1.rs
// Time to implement some traits!
//
// Your task is to implement the trait
// `AppendBar` for the type `String`.
//
// The trait AppendBar has only one function,
// which appends "Bar" to any object
// implementing this trait.
// Execute `rustlings hint traits1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

trait AppendBar {
    fn append_bar(self) -> Self;
}

impl AppendBar for String {
    // TODO: Implement `AppendBar` for type `String`.
}

fn main() {
    let s = String::from("Foo");
    let s = s.append_bar();
    println!("s: {}", s);
    // convert unit test to play in playground
    assert_eq!(
        String::from("Foo").append_bar(),
        String::from("FooBar")
    );

    assert_eq!(
        String::from("").append_bar().append_bar(),
        String::from("BarBar")
    );
}

// #[cfg(test)]
// mod tests {
//     use super::*;
//
//     #[test]
//     fn is_foo_bar() {
//         assert_eq!(String::from("Foo").append_bar(), String::from("FooBar"));
//     }
//
//     #[test]
//     fn is_bar_bar() {
//         assert_eq!(
//             String::from("").append_bar().append_bar(),
//             String::from("BarBar")
//         );
//     }
// }

Hint

A discussion about Traits in Rust can be found at: 🪐Traits: Defining Shared Behavior - The Rust Programming Language

solution: String

impl AppendBar for String {
    //Add your code here
    fn append_bar(self) -> Self {
        format!("{}Bar", self)
        // or
        // format!("{self}Bar")
        // or
        // self + "Bar"
    }
}

traits2: implement the trait for a vector of strings.

// traits2.rs
//
// Your task is to implement the trait
// `AppendBar` for a vector of strings.
//
// To implement this trait, consider for
// a moment what it means to 'append "Bar"'
// to a vector of strings.
//
// No boiler plate code this time,
// you can do this!
// Execute `rustlings hint traits2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

trait AppendBar {
    fn append_bar(self) -> Self;
}

// TODO: Implement trait `AppendBar` for a vector of strings.

// #[cfg(test)]
// mod tests {
//     use super::*;
//
//     #[test]
//     fn is_vec_pop_eq_bar() {
//         let mut foo = vec![String::from("Foo")].append_bar();
//         assert_eq!(foo.pop().unwrap(), String::from("Bar"));
//         assert_eq!(foo.pop().unwrap(), String::from("Foo"));
//     }
// }

fn main() {
    // convert unit test to main to play in playground
    let mut foo = vec![String::from("Foo")].append_bar();
    assert_eq!(foo.pop().unwrap(), String::from("Bar"));
    assert_eq!(foo.pop().unwrap(), String::from("Foo"));
}

Hint

Notice how the trait takes ownership of ‘self’,and returns Self. Try mutating the incoming string vector. Have a look at the tests to see what the result should look like!

Vectors provide suitable methods for adding an element at the end. See the documentation at: Vec in std::vec - Rust

solution: Vec

// Add your code here
impl AppendBar for Vec<String> {
    fn append_bar(mut self) -> Self { // Borrow self as `mut`
        self.push("Bar".to_string());
        self
    }
}

traits3: implement the Licensed trait for both structures and have them return the same information without writing the same function twice.

// traits3.rs
//
// Your task is to implement the Licensed trait for
// both structures and have them return the same
// information without writing the same function twice.
//
// Consider what you can add to the Licensed trait.
// Execute `rustlings hint traits3` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

pub trait Licensed {
    fn licensing_info(&self) -> String;
}

struct SomeSoftware {
    version_number: i32,
}

struct OtherSoftware {
    version_number: String,
}

impl Licensed for SomeSoftware {} // Don't edit this line
impl Licensed for OtherSoftware {} // Don't edit this line

// #[cfg(test)]
// mod tests {
//     use super::*;
//
//     #[test]
//     fn is_licensing_info_the_same() {
//         let licensing_info = String::from("Some information");
//         let some_software = SomeSoftware { version_number: 1 };
//         let other_software = OtherSoftware {
//             version_number: "v2.0.0".to_string(),
//         };
//         assert_eq!(some_software.licensing_info(), licensing_info);
//         assert_eq!(other_software.licensing_info(), licensing_info);
//     }
// }

fn main() {
    // convert unit test to main to play in playground
    let licensing_info = String::from("Some information");
    let some_software = SomeSoftware { version_number: 1 };
    let other_software = OtherSoftware {
        version_number: "v2.0.0".to_string(),
    };
    assert_eq!(some_software.licensing_info(), licensing_info);
}

Hint

Traits can have a default implementation for functions. Structs that implement the trait can then use the default version of these functions if they choose not implement the function themselves.

See the documentation at: 🪐Traits: Defining Shared Behavior >> Default Implementations - The Rust Programming Language

solution: Default Trait Method

pub trait Licensed {
    fn licensing_info(&self) -> String {
        String::from("Some information")
    }
}

traits4: trait bounds

// traits4.rs
//
// Your task is to replace the '??' sections so the code compiles.
// Don't change any line other than the marked one.
// Execute `rustlings hint traits4` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

pub trait Licensed {
    fn licensing_info(&self) -> String {
        "some information".to_string()
    }
}

struct SomeSoftware {}

struct OtherSoftware {}

impl Licensed for SomeSoftware {}

impl Licensed for OtherSoftware {}

// YOU MAY ONLY CHANGE THE NEXT LINE
fn compare_license_types(software: ? ?, software_two: ? ?) -> bool {
    software.licensing_info() == software_two.licensing_info()
}

// #[cfg(test)]
// mod tests {
//     use super::*;
//
//     #[test]
//     fn compare_license_information() {
//         let some_software = SomeSoftware {};
//         let other_software = OtherSoftware {};
//
//         assert!(compare_license_types(some_software, other_software));
//     }
//
//     #[test]
//     fn compare_license_information_backwards() {
//         let some_software = SomeSoftware {};
//         let other_software = OtherSoftware {};
//
//         assert!(compare_license_types(other_software, some_software));
//     }
// }

fn main() {
    // convert unit test to main to play in playground
    // compare_license_information
    let some_software = SomeSoftware {};
    let other_software = OtherSoftware {};

    assert!(compare_license_types(some_software, other_software));

    // compare_license_information_backwards
    let some_software = SomeSoftware {};
    let other_software = OtherSoftware {};

    assert!(compare_license_types(other_software, some_software));
}

Hint

Instead of using concrete types as parameters you can use traits. Try replacing the ‘??’ with ‘impl <what goes here?>’

See the documentation at: 🪐Traits: Defining Shared Behavior >> Traits as Parameters - The Rust Programming Language

solution: impl

fn compare_license_types(software: impl Licensed, software_two: impl Licensed) -> bool {
    software.licensing_info() == software_two.licensing_info()
}

traits5: impl multiple traits

// traits5.rs
//
// Your task is to replace the '??' sections so the code compiles.
// Don't change any line other than the marked one.
// Execute `rustlings hint traits5` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

pub trait SomeTrait {
    fn some_function(&self) -> bool {
        true
    }
}

pub trait OtherTrait {
    fn other_function(&self) -> bool {
        true
    }
}

struct SomeStruct {}
struct OtherStruct {}

impl SomeTrait for SomeStruct {}
impl OtherTrait for SomeStruct {}
impl SomeTrait for OtherStruct {}
impl OtherTrait for OtherStruct {}

// YOU MAY ONLY CHANGE THE NEXT LINE
fn some_func(item: ??) -> bool {
    item.some_function() && item.other_function()
}

fn main() {
    some_func(SomeStruct {});
    some_func(OtherStruct {});
}

Hint

To ensure a parameter implements multiple traits use the ‘+ syntax’. Try replacing the ‘??’ with ‘impl <> + <>’.

See the documentation at: Specifying Multiple Trait Bounds with the + Syntax

solution: impl or where

fn some_func<T>(item: T) -> bool
    where T: SomeTrait + OtherTrait
{
    item.some_function() && item.other_function()
}

fn some_func(item: impl SomeTrait + OtherTrait) -> bool {
    item.some_function() && item.other_function()
}