Index
- Guessing Game
- Common Programming Concepts
- Understanding Ownership
- Using Structs
- Enums and Pattern Matching
- Managing Growing Projects with Packages, Crates, and Modules
- Defining Modules to Control Scope and Privacy
- Paths for Referring to an Item in the Module Tree
- Bringing Paths into Scope with the use Keyword
- Separating Modules into Different Files
- Common Collections
- Error Handling
- Generic Types, Traits, and Lifetimes
- Writing Automated Tests
- Object Oriented Programming
- Adding dependancies
- Option Take
- RefCell
- mem
- Data Structure
- Recipe
- Semi colon
- Calling rust from python
- Default
- Crytocurrency With rust
- Function chaining
- Question Mark Operator
- Tests with println
- lib and bin
- Append vector to hash map
- Random Number
- uuid4
- uwrap and option
- Blockchain with Rust
- Near Protocol
- Actix-web
Traits: Defining Shared Behavior
A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic can be any type that has certain behavior.
Defining a Trait
A type’s behavior consists of the methods we can call on that type. Different types share the same behavior if we can call the same methods on all of those types
For example, let’s say we have multiple structs that hold various kinds and amounts of text: a
NewsArticle
struct that holds a news story filed in a particular location and a Tweet
that can have at most 280 characters along with metadata that indicates whether it was a new tweet, a retweet, or a reply to another tweet.We want to make a media aggregator library that can display summaries of data that might be stored in a
NewsArticle
or Tweet
instance. To do this, we need a summary from each type, and we need to request that summary by calling a summarize
method on an instance. Listing 10-12 shows the definition of a Summary
trait that expresses this behavior.pub trait Summary {
fn summarize(&self) -> String;
}
fn summarize(&self) -> String;
}
A
Summary
trait that consists of the behavior provided by a summarize
methodHere, we declare a trait using the
trait
keyword and then the trait’s name, which is Summary
in this case. Inside the curly brackets, we declare the method signatures that describe the behaviors of the types that implement this trait, which in this case is fn summarize(&self) -> String
.A trait can have multiple methods in its body: the method signatures are listed one per line and each line ends in a semicolon.
Implementing a Trait on a Type
Filename: src/lib.rs
#![allow(unused_variables)]
fn main() {
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
}
Implementing a trait on a type is similar to implementing regular methods. The difference is that after
impl
, we put the trait name that we want to implement, then use the for
keyword, and then specify the name of the type we want to implement the trait for.After implementing the trait, we can call the methods on instances of
NewsArticle
and Tweet
in the same way we call regular methods, like this: let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
Default Implementations
Sometimes it’s useful to have default behavior for some or all of the methods in a trait instead of requiring implementations for all methods on every type.
Listing below shows how to specify a default string for the
summarize
method of the Summary
trait instead of only defining the method signatureFilename: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
To use a default implementation to summarize instances of
NewsArticle
instead of defining a custom implementation, we specify an empty impl
block with impl Summary for NewsArticle {}
.Even though we’re no longer defining the
summarize
method on NewsArticle
directly, we’ve provided a default implementation and specified that NewsArticle
implements the Summary
trait. As a result, we can still call the summarize
method on an instance of NewsArticle
, like this: let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
Creating a default implementation for
summarize
doesn’t require us to change anything about the implementation of Summary
on Tweet
The reason is that the syntax for overriding a default implementation is the same as the syntax for implementing a trait method that doesn’t have a default implementation.
Default implementations can call other methods in the same trait, even if those other methods don’t have a default implementation. In this way, a trait can provide a lot of useful functionality and only require implementors to specify a small part of it.
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
To use this version of
Summary
, we only need to define summarize_author
when we implement the trait on a type:impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
After we define
summarize_author
, we can call summarize
on instances of the Tweet
struct, and the default implementation of summarize
will call the definition of summarize_author
that we’ve provided. Because we’ve implemented summarize_author
, the Summary
trait has given us the behavior of the summarize
method without requiring us to write any more code. let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
This code prints
1 new tweet: (Read more from @horse_ebooks...)
.Traits as Parameters
Now that you know how to define and implement traits, we can explore how to use traits to define functions that accept many different types.
For example, we implemented the
Summary
trait on the NewsArticle
and Tweet
types. We can define a notify
function that calls the summarize
method on its item
parameter, which is of some type that implements the Summary
trait. To do this, we can use the impl Trait
syntax, like this:pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
println!("Breaking news! {}", item.summarize());
}
Instead of a concrete type for the
item
parameter, we specify the impl
keyword and the trait name. This parameter accepts any type that implements the specified trait. In the body of notify
, we can call any methods on item
that come from the Summary
trait, such as summarize
. We can call notify
and pass in any instance of NewsArticle
or Tweet
Trait Bound Syntax
The
impl Trait
syntax works for straightforward cases but is actually syntax sugar for a longer form, which is called a trait bound; it looks like this:pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
println!("Breaking news! {}", item.summarize());
}
The
impl Trait
syntax is convenient and makes for more concise code in simple cases. The trait bound syntax can express more complexity in other cases. For example, we can have two parameters that implement Summary
. Using the impl Trait
syntax looks like this:pub fn notify(item1: impl Summary, item2: impl Summary) {
If we wanted this function to allow
item1
and item2
to have different types, using impl Trait
would be appropriate (as long as both types implement Summary
). If we wanted to force both parameters to have the same type, that’s only possible to express using a trait bound, like this:pub fn notify<T: Summary>(item1: T, item2: T) {
Specifying Multiple Trait Bounds with the + Syntax
We can also specify more than one trait bound. Say we wanted
notify
to use display formatting on item
as well as the summarize
method: we specify in the notify
definition that item
must implement both Display
and Summary
. We can do so using the +
syntax:pub fn notify(item: impl Summary + Display) {
The
+
syntax is also valid with trait bounds on generic types:pub fn notify<T: Summary + Display>(item: T) {
Clearer Trait Bounds with where Clauses
Using too many trait bounds has its downsides. Each generic has its own trait bounds, so functions with multiple generic type parameters can contain lots of trait bound information between the function’s name and its parameter list, making the function signature hard to read. For this reason, Rust has alternate syntax for specifying trait bounds inside a
where
clause after the function signature. So instead of writing this:fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
we can use a
where
clause, like thisfn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
where T: Display + Clone,
U: Clone + Debug
{
This function’s signature is less cluttered: the function name, parameter list, and return type are close together, similar to a function without lots of trait bounds.
Returning Types that Implement Traits
/// Snip
Fixing the largest Function with Trait Bounds
In the body of
largest
we wanted to compare two values of type T
using the greater than (>
) operator. Because that operator is defined as a default method on the standard library trait std::cmp::PartialOrd
, we need to specify PartialOrd
in the trait bounds for T
so the largest
function can work on slices of any type that we can compare. We don’t need to bring PartialOrd
into scope because it’s in the prelude. Change the signature of largest
to look like this:fn largest<T: PartialOrd>(list: &[T]) -> T {
This time when we compile the code, we get a different set of errors:
To call this code with only those types that implement the
Copy
trait, we can add Copy
to the trait bounds of T
!fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
f we don’t want to restrict the
largest
function to the types that implement the Copy
trait, we could specify that T
has the trait bound Clone
instead of Copy
. Then we could clone each value in the slice when we want the largest
function to have ownership. Using the clone
function means we’re potentially making more heap allocations in the case of types that own heap data like String
, and heap allocations can be slow if we’re working with large amounts of data.Another way we could implement
largest
is for the function to return a reference to a T
value in the slice. If we change the return type to &T
instead of T
, thereby changing the body of the function to return a reference, we wouldn’t need the Clone
or Copy
trait bounds and we could avoid heap allocations. Try implementing these alternate solutions on your own!Using Trait Bounds to Conditionally Implement Methods
#![allow(unused_variables)]
fn main() {
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
}