17 Rustlings Traits Solution

Traits

From the README

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. Will allow a user to write the following:

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

Traits1.rs

// 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);
}

#[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")
        );
    }
}

In this first Trait exercise we are tasked with implementing the trait AppendBar for the type String. We get the additional details that the AppendBar has only one function and it appends "Bar" to any object that is implementing this trait. Now, let's look at the errors.

Traits1.rs errors

⚠️  Compiling of exercises/traits/traits1.rs failed! Please try again. Here's the output:
error[E0046]: not all trait items implemented, missing: `append_bar`
  --> exercises/traits/traits1.rs:18:1
   |
15 |     fn append_bar(self) -> Self;
   |     ---------------------------- `append_bar` from trait
...
18 | impl AppendBar for String {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^ missing `append_bar` in implementation

error: aborting due to previous error

Nothing special here, so let's move on to solving our problem.

Traits1.rs solution

Alright so again, our job is to finish implementing the AppendBar trait and I can think of a couple of different ways of doing this so let's try the easiest. First we start by creating the function inside of our impl block. We know we have to define a function called append_bar and we also know that it should return a String, and the other thing we know is that we need to pass in self as a parameter.

impl AppendBar for String {
    fn append_bar(self) -> String{

    }
}

This is our basic structure but now we have to make it do something inside of the body...however, if we save the code at this point, we get a big hint from the compiler.

⚠️  Compiling of exercises/traits/traits1.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
  --> exercises/traits/traits1.rs:20:28
   |
20 |     fn append_bar(self) -> String {}
   |        ----------          ^^^^^^ expected `String`, found `()`
   |        |
   |        implicitly returns `()` as its body has no tail or `return` expression
   |
help: consider returning the local binding `self`
   |
20 |     fn append_bar(self) -> String { self }
   |                                     ++++

error: aborting due to previous error

This error shows us that we are not returning anything, which is clear to us because we are not doing anything inside of our function body, however it does give us the a big hint help: consider returning the local binding self, so let's try that!

impl AppendBar for String {
    // TODO: Implement `AppendBar` for type `String`.
    fn append_bar(self) -> String {
        self // returning `self` here note no `;`
    }
}

By returning self we are now compiling, but we still have a problem, we are not passing our tests:

⚠️  Testing of exercises/traits/traits1.rs failed! Please try again. Here's the output:

running 2 tests
test tests::is_foo_bar ... FAILED
test tests::is_bar_bar ... FAILED

successes:

successes:

failures:

---- tests::is_foo_bar stdout ----
thread 'tests::is_foo_bar' panicked at 'assertion failed: `(left == right)`
  left: `"Foo"`,
 right: `"FooBar"`', exercises/traits/traits1.rs:39:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- tests::is_bar_bar stdout ----
thread 'tests::is_bar_bar' panicked at 'assertion failed: `(left == right)`
  left: `""`,
 right: `"BarBar"`', exercises/traits/traits1.rs:44:9


failures:
    tests::is_bar_bar
    tests::is_foo_bar

test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

We can clearly see the issue is that our code is not adding or appending Bar to our String

thread 'tests::is_foo_bar' panicked at 'assertion failed: `(left == right)`
  left: `"Foo"`,
 right: `"FooBar"`'

We see that left !== right so what's the easiest way to fix that?

Can we just add Bar? Let's try...

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

Yep and with that we are compiling and passing our tests.

✅ Successfully tested exercises/traits/traits1.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

I did mentioned that there might be 2 different ways to do this, I was thinking about using the format! macro, let's see if that works. So instead of simply returning self + "Bar" , we can use format! to layout the contents of the String.

impl AppendBar for String {
    // TODO: Implement `AppendBar` for type `String`.
    fn append_bar(self) -> String {
        format!("{}{}", self, "Bar")
    }
}

This also works! So there you have it, two different ways to solve this problem. It's really a matter of preference and although they're both valid they have different pro's and con's:

Using format!

  1. Flexibility: The format! macro is more flexible and can handle complex string manipulations, including variable interpolation and formatting options.
  2. Readability: For more complex operations, format! can be easier to read and understand.

Using +

  1. Simplicity: The + operator is simpler and more straightforward for basic string concatenation tasks.
  2. Performance: For very simple concatenations, using + can be slightly more efficient because it doesn't involve parsing a format string.

Final Code

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

impl AppendBar for String {
    // TODO: Implement `AppendBar` for type `String`.
    fn append_bar(self) -> String {
        format!("{}{}", self, "Bar")
    }
}

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

Traits2.rs

// 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"));
    }
}

Our instructions are to create our impl block for the AppendBar function. We get zero boilerplate code and we have to consider what it means to append "Bar". By looking at code we see that the trait has a method append_bar that should modify the vector by appending the string "Bar" to it.

Traits2.rs errors

⚠️  Compiling of exercises/traits/traits2.rs failed! Please try again. Here's the output:
error[E0599]: no method named `append_bar` found for struct `Vec<String>` in the current scope
  --> exercises/traits/traits2.rs:28:49
   |
28 |         let mut foo = vec![String::from("Foo")].append_bar();
   |                                                 ^^^^^^^^^^ help: there is a method with a similar name: `append`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
note: `AppendBar` defines an item `append_bar`, perhaps you need to implement it
  --> exercises/traits/traits2.rs:16:1
   |
16 | trait AppendBar {
   | ^^^^^^^^^^^^^^^

warning: unused import: `super`
  --> exercises/traits/traits2.rs:24:9
   |
24 |     use super::*;
   |         ^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default

error: aborting due to previous error; 1 warning emitted

Our errors show what we should expect and tell us that items from traits can only be used if the trait is implemented in scope. Alright although it's nothing specifically new, it's nice how the compiler also gives you the hint that perhaps you need to implement it. Alright moving on.

Traits2.rs solution

So let's start implementing, we had something similar in the previous exercise, right? So it shouldn't be too difficult. What we need to do is tell Rust how a Vec<String> should behave when the append_bar method is called. We do this by implementing the AppendBar trait. If we remember our instructions we are reminded to think of what it means to append "Bar" to a vector of String we doesn't that mean that we need to .push() on to the Vector? Let's try

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

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

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

Here's what's happening in this block:

  • impl AppendBar for Vec<String>: This line says we're implementing the AppendBar trait specifically for Vec<String>.
  • fn append_bar(mut self) -> Vec<String>: This is the method we're implementing. It takes a mutable version of self (the Vec<String> you're calling the method on) and returns a new Vec<String>.
  • self.push(String::from("Bar")): This line appends the string "Bar" to the vector.
  • self: Finally, you return the modified vector.

Let's save and see if that does the trick.

Yes, we've done it!

✅ Successfully tested exercises/traits/traits2.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

Traits3.rs

// 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);
    }
}

We have two structs, SomeSoftware and OtherSoftware, and a trait Licensed with a method licensing_info. The task is to implement the Licensed trait for both structs so that they return the same licensing information. The challenge is to do this without writing the same function twice.

Traits3.rs errors

⚠️  Compiling of exercises/traits/traits3.rs failed! Please try again. Here's the output:
error[E0046]: not all trait items implemented, missing: `licensing_info`
  --> exercises/traits/traits3.rs:24:1
   |
13 |     fn licensing_info(&self) -> String;
   |     ----------------------------------- `licensing_info` from trait
...
24 | impl Licensed for SomeSoftware {} // Don't edit this line
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `licensing_info` in implementation

error[E0046]: not all trait items implemented, missing: `licensing_info`
  --> exercises/traits/traits3.rs:25:1
   |
13 |     fn licensing_info(&self) -> String;
   |     ----------------------------------- `licensing_info` from trait
...
25 | impl Licensed for OtherSoftware {} // Don't edit this line
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `licensing_info` in implementation

error: aborting due to 2 previous errors

Errors tell us we have to implement our traits, let's see how we can do this.

Traits3.rs solution

One way to achieve this is to provide a default implementation for the licensing_info method within the Licensed trait itself. This way, both structs will inherit this default behavior, and we won't have to implement the method twice.

pub trait Licensed {
	// Provide a default implementation for the licensing_info method
	fn licensing_info(&self) -> String {
		String::from("Some information")
	}
}
  1. Default Implementation in Trait: In the Licensed trait, we provide a default implementation for the licensing_info method. This default implementation returns a string "Some information".

  2. Implementing the Trait for Structs: We keep the lines impl Licensed for SomeSoftware {} and impl Licensed for OtherSoftware {} as they are. Since we've provided a default implementation in the trait, these structs will inherit that behavior.

  3. Tests: The test function is_licensing_info_the_same should now pass, as both SomeSoftware and OtherSoftware will return "Some information" when their licensing_info methods are called.

Great! It works our code is compiling and our tests are passing

✅ Successfully tested exercises/traits/traits3.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

Traits4.rs

// 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));
    }
}

This problem tells us that we can only change the code where we see the ?? question marks. So let's take a look at the errors as we always do but I'm guessing there will be no surprises there.

Traits4.rs errors

⚠️  Compiling of exercises/traits/traits4.rs failed! Please try again. Here's the output:
error: expected identifier, found `,`
  --> exercises/traits/traits4.rs:23:38
   |
23 | fn compare_license_types(software: ??, software_two: ??) -> bool {
   |                                      ^
   |                                      |
   |                                      expected identifier
   |                                      help: remove this comma

error: expected a trait, found type
  --> exercises/traits/traits4.rs:23:37

As expected, it points out where the missing code is and offers a suggestion although it's wrong, it's trying to figure out what is happening, so we can't get to upset the compiler.

Traits4.rs solution

So what does the compiler need to see inside of the parenthesis? Let's break it down by first looking at what we already have.

The Trait and Structs

First, we have a trait Licensed with a default implementation for a method called licensing_info. This method returns a string "some information".

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

Then, we have two structs, SomeSoftware and OtherSoftware, that implement this trait.

struct SomeSoftware {}
struct OtherSoftware {}

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

By doing this, both structs inherit the default behavior of the licensing_info method from the Licensed trait.

The Function to Compare Licenses

Now we get to the problem. We have the function compare_license_types takes two parameters, software and software_two. Both of which need to implement the Licensed trait. We do this by adding the by the syntax impl Licensed where the ?? were, and this should solve our problem.

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

Here's what's happening:

  • software: impl Licensed: This means that the function expects a parameter software that implements the Licensed trait.
  • software_two: impl Licensed: Similarly, this function expects another parameter software_two that also implements the Licensed trait.
  • software.licensing_info() == software_two.licensing_info(): This line compares the licensing information of both software. Since both SomeSoftware and OtherSoftware implement the Licensed trait, they both have a licensing_info method that returns "some information".

with the added impl Licensed parameters our code compiles and passes it's test. Now let's move on to our final exercise on Traits.

Traits5.rs

// 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 {});
}

We get a similar type of problem where we have to replace the ?? question marks, and it's only in one spot. Let's look at the errors for any hints.

Traits5.rs errors

⚠️  Compiling of exercises/traits/traits5.rs failed! Please try again. Here's the output:
error: expected identifier, found `)`
  --> exercises/traits/traits5.rs:30:22
   |
30 | fn some_func(item: ??) -> bool {
   |                      ^ expected identifier

error[E0425]: cannot find value `item` in this scope
  --> exercises/traits/traits5.rs:31:5
   |
31 |     item.some_function() && item.other_function()
   |     ^^^^ not found in this scope

error[E0425]: cannot find value `item` in this scope
  --> exercises/traits/traits5.rs:31:29
   |
31 |     item.some_function() && item.other_function()
   |                             ^^^^ not found in this scope

error: aborting due to 3 previous errors

We have 3 errors all together, one pointing at the expected identifier where the questions marks are and the next 2 pointing at the item not found in scope.

Traits5.rs solution

If we look at our code we have a similar set-up to the previous exercise but we have two different traits SomeTrait and OtherTrait since we have been instructed to change only the parameter in some_func we can deduce that it will be a similar situation as the the previous exercise with using the impl and the Trait however in this case we have two different traits so how do we handle that? Easily enough we can use the + syntax to use allow our function to take both of these traits. This would look something like this: impl <> + <> so let's try this.

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

And with this it compiles!

Conclusion

In this post, we explored various exercises related to traits in Rust. Here's a quick summary of what we learned:

Traits in Rust

  • Traits are similar to interfaces in other languages.
  • They define shared behavior across data types.
  • Traits can have default implementations.

Exercises

Traits1.rs

  • Implemented the AppendBar trait for String.
  • Explored two ways to append "Bar" to a string: using + and format!.

Traits2.rs

  • Implemented the AppendBar trait for Vec<String>.
  • Used the push method to append "Bar" to the vector.

Traits3.rs

  • Provided a default implementation for the licensing_info method in the Licensed trait.
  • Both SomeSoftware and OtherSoftware structs inherited this default behavior.

Traits4.rs

  • Used impl Licensed to specify that the function compare_license_types expects parameters that implement the Licensed trait.

Traits5.rs

  • Used impl SomeTrait + OtherTrait to specify that the function some_func expects a parameter that implements both SomeTrait and OtherTrait.

By working through these exercises, we gained a deeper understanding of how traits work in Rust and how they can be used to write more flexible and reusable code.