Skip to main content

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.
  • References:

traits1.rs

// The trait `AppendBar` has only one function which appends "Bar" to any object
// implementing this trait.
trait AppendBar {
fn append_bar(self) -> Self;
}

impl AppendBar for String {
// Implement `AppendBar` for the type `String`.
fn append_bar(self) -> Self {
self + "Bar"
}
}

fn main() {
let s = String::from("Foo");
let s = s.append_bar();
println!("s: {s}");
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn is_foo_bar() {
assert_eq!(String::from("Foo").append_bar(), "FooBar");
}

#[test]
fn is_bar_bar() {
assert_eq!(String::from("").append_bar().append_bar(), "BarBar");
}
}
  • This exercise is easy we just need to implement trait item/function append_bar inside the impl AppendBar for String block.

traits2.rs

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

// Implement the trait `AppendBar` for a vector of strings.
// `append_bar` should push the string "Bar" into the vector.
impl AppendBar for Vec<String> {
fn append_bar(mut self) -> Self {
self.push("Bar".to_string());
self
}
}

fn main() {
// You can optionally experiment here.
}

#[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(), "Bar");
assert_eq!(foo.pop().unwrap(), "Foo");
}
}
  • This exercise also similar like the first one.

  • We need to create implementation of AppendBar trait for Vec<String>

  • Because we want to mutate the vector (self) we need to add mut syntax.

  • The full implementation will look like this:

    impl AppendBar for Vec<String> {
    fn append_bar(mut self) -> Self {
    self.push("Bar".to_string());
    self
    }
    }

traits3.rs

trait Licensed {
// Add a default implementation for `licensing_info` so that
// implementors like the two structs below can share that default behavior
// without repeating the function.
// The default license information should be the string "Default license".
fn licensing_info(&self) -> String {
"Default license".to_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.

fn main() {
// You can optionally experiment here.
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn is_licensing_info_the_same() {
let licensing_info = "Default license";
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);
}
}
  • In this exercise we need to create default implementation of licensing_info that will return "Default license".
  • This default implementation will be called if not the trait is not implemented for the specified type.

traits4.rs

trait Licensed {
fn licensing_info(&self) -> String {
"Default license".to_string()
}
}

struct SomeSoftware;
struct OtherSoftware;

impl Licensed for SomeSoftware {}
impl Licensed for OtherSoftware {}

// Fix the compiler error by only changing the signature of this function.
fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool {
software1.licensing_info() == software2.licensing_info()
}

fn main() {
// You can optionally experiment here.
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn compare_license_information() {
assert!(compare_license_types(SomeSoftware, OtherSoftware));
}

#[test]
fn compare_license_information_backwards() {
assert!(compare_license_types(OtherSoftware, SomeSoftware));
}
}
  • In this exercise we need to change the function signature of compare_license_types to accept both SomeSoftware and OtherSoftware type.

  • We can do this using generics.

  • Or we can also use impl Trait syntax like this:

    fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool
  • You can read more about it in here: https://doc.rust-lang.org/book/ch10-02-traits.html#trait-bound-syntax

traits5.rs

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

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

struct SomeStruct;
impl SomeTrait for SomeStruct {}
impl OtherTrait for SomeStruct {}

struct OtherStruct;
impl SomeTrait for OtherStruct {}
impl OtherTrait for OtherStruct {}

// Fix the compiler error by only changing the signature of this function.
fn some_func(item: (impl SomeTrait + OtherTrait)) -> bool {
item.some_function() && item.other_function()
}

fn main() {
// You can optionally experiment here.
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_some_func() {
assert!(some_func(SomeStruct));
assert!(some_func(OtherStruct));
}
}
  • In this exercise we need to update function signature of some_func to accept implementation of SomeTrait and OtherTrait.

  • It means that given type must implement both trait.

  • We can do this by using + syntax like this:

    fn some_func(item: (impl SomeTrait + OtherTrait)) -> bool