Zum Hauptinhalt springen

Traits and Lifetime in Rust

Traits in Rust

Fills a similar purpose to interfaces or abstract classes in other languages. The compiler will enforce that any type implementing a Trait must implement all it's methods.

Some really common Rust traits include:

  • Clone the clone() method
  • Display to allow formatted display/println via {}
  • Debug to allow formatted display/println via {:?} and {:#?}
// the trait definition
pub trait Summary {
fn summarize(&self) -> String;
}
// a struct
pub struct NewsFeed {
pub headline: String,
pub author: String,
pub content: String,
}
// above struct implements the Summary trait
impl Summary for NewsFeed {
fn summarize(&self) -> String{
format!("{} \n {}, written by {}", self.headline, self.content, self.author)
}
}

fn main(){
let news = NewsFeed{
headline: String::from("A Story"),
author: String::from("by Me"),
content: String::from("Some lorem Ipsum never fails to impress..."),
};
println!("{}", news.summarize())
}

default implementation for Traits

pub trait Summary {
fn summarize(&self) -> String{
String::from("This is a default value")
}
}

Traits in Parameters

// syntax with impl
pub fn some_func_printer(input: &impl Summary) {
println!("{}", input.summarize());
}
// trait bound syntax forcing both inputs to the the same type
pub fn some_func_printer<T: Summary>(input1: &T, input2: &T) {
println!("{} and {}", input1.summarize(), input2.summarize());
}

// forcing more than one traits at once:
pub fn func(input: &(impl Summary + Display)){}
pub fn func<T: Summary + Display>(input: &T){}

// alternate traits syntax with where:
fn func<T:Display + Clone, U: clone+ + Debug>(in1: &T, in2: &U) {}
fn func<T, U>(in1: &T, in2: &U)
where
T: Display + Clone,
U: Clone + Debug,
{}

// returning Types that implement Traits
fn returns_summ() -> impl Summary{
NewsFeed{
headline: String::from("A Story"),
author: String::from("by Me"),
content: String::from("Some lorem Ipsum never fails to impress..."),
}
}

conditionally implement Methods:

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

Lifetime syntax

&i32        // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

References with Lifetimes (functions)

Lifetimes ensure that a reference is valid as long as we need it. Most of the time lifetimes are implicid and inferred, compare Borrowing Rules.

  • Lifetime annotations don’t change how long any of the references live. Rather, they describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes.
  • The following will NOT COMPILE without the lifetime annotation 'a added in. (we are basically asserting that s1 s2 and the return value all have the same lifetime.)
fn main() {
let str1 = String::from("1234");
let str2 = "abcde";

let result = longest(str1.as_str(), str2);
println!("The longest string is {}", result);
}

// fn longest(s1: &str,s2: &str) -> &str { // would not compile
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len(){
s1
} else {
s2
}
}

We just defined that the returned stringslice lives as long as the minimum between s1 and s2 lifetime.

References with Lifetimes (structs)

As long as a struct only holds owned types there is no need for lifetime annotations. But once we define a struct to hold references we need to add those:

struct Excerpt<'a> {
part: &'a str,
}

fn main() {
let novel = String::from("Some Lorem Ipsum. blah blah...");
let first_sentence = novel.split('.').next().expect("Failed no . found");
let ex = Excerpt {
part: first_sentence
};
println!("{:?}", ex.part)
}

References with Lifetime (struct-methods)

Lifetime names for struct fields always need to be declared after the impl keyword:

impl <'a> excerpt <'a> {
fn level(&self) -> i32{
3
}
}

static lifetime

All string literals have the 'staticlifetime. The text is stored directly in the binary which is always available.

let s: &'static str = "I have a static lifetime.";