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
(theclone
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!
- Flexibility: The
format!
macro is more flexible and can handle complex string manipulations, including variable interpolation and formatting options. - Readability: For more complex operations,
format!
can be easier to read and understand.
Using +
- Simplicity: The
+
operator is simpler and more straightforward for basic string concatenation tasks. - 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 theAppendBar
trait specifically forVec<String>
.fn append_bar(mut self) -> Vec<String>
: This is the method we're implementing. It takes a mutable version ofself
(theVec<String>
you're calling the method on) and returns a newVec<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")
}
}
-
Default Implementation in Trait: In the
Licensed
trait, we provide a default implementation for thelicensing_info
method. This default implementation returns a string "Some information". -
Implementing the Trait for Structs: We keep the lines
impl Licensed for SomeSoftware {}
andimpl Licensed for OtherSoftware {}
as they are. Since we've provided a default implementation in the trait, these structs will inherit that behavior. -
Tests: The test function
is_licensing_info_the_same
should now pass, as bothSomeSoftware
andOtherSoftware
will return "Some information" when theirlicensing_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 parametersoftware
that implements theLicensed
trait.software_two: impl Licensed
: Similarly, this function expects another parametersoftware_two
that also implements theLicensed
trait.software.licensing_info() == software_two.licensing_info()
: This line compares the licensing information of both software. Since bothSomeSoftware
andOtherSoftware
implement theLicensed
trait, they both have alicensing_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 forString
. - Explored two ways to append "Bar" to a string: using
+
andformat!
.
Traits2.rs
- Implemented the
AppendBar
trait forVec<String>
. - Used the
push
method to append "Bar" to the vector.
Traits3.rs
- Provided a default implementation for the
licensing_info
method in theLicensed
trait. - Both
SomeSoftware
andOtherSoftware
structs inherited this default behavior.
Traits4.rs
- Used
impl Licensed
to specify that the functioncompare_license_types
expects parameters that implement theLicensed
trait.
Traits5.rs
- Used
impl SomeTrait + OtherTrait
to specify that the functionsome_func
expects a parameter that implements bothSomeTrait
andOtherTrait
.
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.