We’re done! We’ll compile and run our program with cargo run
in the code directory.
Alas! We have an error:
error[E0599]: no method named `try_into` found for type `usize` in the current scope
--> src/main.rs:10:25
|
10 | var.chars().count().try_into().expect("Length of name is too large!")
| ^^^^^^^^ method not found in `usize`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
1 | use std::convert::TryInto;
|
error: aborting due to previous error
For more information about this error, try `rustc --explain E0599`.
error: could not compile `love_calculator`.
Something about traits.
A not very useful example
So, what are traits? Well, traits are a way Rust says: Inheritance.
As codebase increases, we notice that some structs seem to have similar characteristics, and it is important that future structs with such characteristics should be able to perform actions these structs can do.
An example, as culled from the Rust documentation will be used. Let’s say we’re implementing Tweets like so:
struct Tweet {
username: String,
content: String,
location: String,
retweet: bool,
}
We might want to implement a summarize
method that displays a short form of the tweet:
impl Tweet {
fn summarize(&self) → String {
if self.retweet {
format!(“{} retweeted: {} while in {}”, self.username, self.content, self.location)
else {
format!(“{} tweeted: {} while in {}”, self.username, self.content, self.location)
}
}
}
Consider another code where we implemented a news article like so:
struct Article {
title: String,
author: String,
content: String,
}
and need to implement a summarize method. While we could as well implement it as a separate method:
impl Article {
fn summarize(&self) → String {
format!(“{} posted an article title: {}”, self.author, self.title)
}
}
we could standardize our code by creating a trait both structs could implement. This way, we can ensure consistency when working with different structs with similar functionality.
So our trait is implemented like so:
trait Summary {
fn summarize(&self) → String
}
Next, we attach the trait to our structs and implement the methods since it would be enforced anyway:
// for Tweet
impl Summary for Tweet {
fn summarize(&self) → String {
if self.retweet {
format!(“{} retweeted: {} while in {}”, self.username, self.content, self.location)
else {
format!(“{} tweeted: {} while in {}”, self.username, self.content, self.location)
}
}
}
// for Article
impl Summary for Article {
fn summarize(&self) → String {
format!(“{} posted an article title: {}”, self.author, self.title)
}
}
Then we can call our methods:
fn main() {
let tweet = Tweet {
String::from(“Lord_Sarcastic”),
String::from(“Hello, world! Bye, world!”),
String::from(“Lagos”),
false
}
let article = Article {
String::from(“Python and Rust?”),
String::from(“Lord_Sarcastic”),
String::from(“Python and Rust make a good match”)
}
println!(“Tweet summary: ‘{}’”, tweet.summarize());
println!(“Article summary: ‘{}’”, article.summarize());
}
A very useful example
With traits, we could also have a default implementation for methods. As an example, if we wanted a display method that made use of the summarize
method under the hood, we could have a trait like so:
trait Summary {
fn summarize(&self) → String;
fn display(&self) → String {
format!(“Summary: {}”, self.summarize())
}
}
At this point, both structs can call display
without having implemented methods for it, but summarize
must be implemented for it to work.
Lest we go offtrack…
Why this? The thing is for us to use functionalities that involve traits, the trait has to be in scope.
We can see that the Rust compiler insists that we bring a trait into scope before it would allow type conversion. That can be fixed with a simple line:
use std::convert:TryInto;