Convenient ways of type conversions
- T is Deref<U> : *(T) -> U
- T is AsRef<U>: &T -> &U
- T is Borrow<Borrowed>: save cost to allow some convenient ways during function calls. T can be borrowed by some types of Borrowed
Deref
- & -> get reference of the var
- * -> deref an address
Without the Deref
trait, the compiler can only dereference &
references. The deref
method gives the compiler the ability to take a value of any type that implements Deref
and call the deref
method to get a &
reference that it knows how to dereference.
The reason the deref
method returns a reference to a value and that the plain dereference outside the parentheses in *(y.deref())
is still necessary is the ownership system. If the deref
method returned the value directly instead of a reference to the value, the value would be moved out of self
. We don’t want to take ownership of the inner value inside MyBox<T>
in this case or in most cases where we use the dereference operator.
// def of the trait Deref
type trait Deref {
type Target : ?Sized;
fn deref(&self) -> &Self::Target;
}// impl example
impl ops::Deref for String {
type Target = str;
fn deref(&self) -> &str {
unsafe {
str::from_utf8_unchecked(&self.vec)
}
}
}// cast &String to &str
fn main() {
let x = "hello".to_string();
// or use x.deref()
// *x -> *(x.deref()) -> str type match &*x {
"hello" => {println!("!")},
_ -> {}
}
}
AsRef is a convenient to do reference to reference conversion.
Good for a wrapper struct to collect its inner type.
pub trait AsRef<T>
where T: ?Sized, {
fn as_ref(&self) -> &T;
}
Borrow
Borrow trait allows caller to supply any one of multiple essentially identically variants of the same type.
Types express that they can be borrowed as some type T
by implementing Borrow<T>
, providing a reference to a T
in the trait’s borrow
method. A type is free to borrow as several different types.
Borrow trait has a blanket implementation of Borrow<T> for T, &T, &mut T
pub trait Borrow<Borrowed> {
fn borrow(&self) -> &Borrowed;
}
Ex: hashmap
HashMap<K, V>
owns both keys and values. If the key’s actual data is wrapped in a managing type of some kind, it should, however, still be possible to search for a value using a reference to the key’s data. For instance, if the key is a string, then it is likely stored with the hash map as a String
, while it should be possible to search using a &str
use std::borrow::Borrow;
use std::hash::Hash;
pub struct HashMap<K, V> {
// omitted
}
impl<K, V> HashMap<K, V> {
pub fn insert(&self, key: K, value: V) -> Option<T>
where K: Hash + Eq
{}
pub fn get<Q>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + Sized
{
}
}
// the reason why we can use hashmap.get("str")
impl Borrow<str> for String
The trait to copy data from borrowed data
pub trait ToOwned {
type Owned: Borrow<Self>;
// create owned data from borrowed data
fn to_owned(&self) -> Self::Owned;
// use borrowed data to replace owned data
fn clone_into(&self, target: &mut Self::Owned){..}
}
// String implement Borrow<str>
let s: &str = "a";
let ss: String = s.to_owned();
let mut s: String;
"hello".clone_into(&mut s); // s becomes "hello"
Traits as the tags
implementing a trait to a struct is somewhat like labeling a tag on it
- Sized :Types ensure the size during compiled period
- Unsize: Types that can be “unsized” to a dynamically-sized type.
- Copy: Types whose values can be duplicated simply by copying bits.
- Clone: A common trait for the ability to explicitly duplicate an object.
- Send: Types that can be transferred across thread boundaries.
- Sync: Types share ref safely between threads
pub trait Clone : Sized {
fn clone(&self) -> Self;
fn clone_from(&mut self, source: &Self){
*self = source.clone();
}
}// example of implementing Clone trait
impl Clone for T {
fn clone(&self) -> T {
*self
}
}#[derive(Copy, Clone)]
struct TStruct;let t = TStruct{};
let tc = t.clone(); // call clone
Traits for Comparison
- PartialEq, Eq : (a == b, b == a), (a == b, b == c, a == c) for equality (to use assert_eq!)
pub trait Eq : PartialEq<Self> {}
- PartialOrd : trait for values to be compared in a sorted order. it implements partial_cmp, le, lt, ge, gt
pub enum Ordering {
Less,
Equal,
Greater,
}pub trait PartialOrd<Rhs=Self> : PartialEq<Rhs>
where Rhs : ?Sized,
{
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
fn le(&self, other: &Rhs) -> bool;
fn lt(&self, other: &Rhs) -> bool;
fn ge(&self, other: &Rhs) -> bool;
fn gt(&self, other: &Rhs) -> bool;
}
- Ord: The type forms a total order. It implements max, min, cmp,lamp . you must define an implementation for
cmp for trait Ord
pub trait Ord : Eq + PartialOrd<Self> {
fn cmp(&self, other: &Self) -> Ordering;
fn max(&self, other: &Self) -> Self;
fn min(&self, other: &Self) -> Self;
fn clamp(&self, min: &Self, max: &Self) -> Self{..}
Trait from & into
pub trait From<T> {
fn from(T) -> Self;
}pub trait Into<T> {
fn into(self) -> T;
}impl<T, U> Into<U> for T where U : From<T>;// String & str
fn main() {
let a_string = "hello".to_string();
let b_string = String::from("hello");}
Trait Iterator
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}impl Iterator for LimitCounter{
type Item = usize;
fn next(&mut self) -> Option<usize> {
self.start += 1;
if self.start <= self.limit {
Some(self.start)
} else {
None
}
}
}// step by step in each iteration
pub struct Step<I> {
iter: I,
skip: usize,
}
// impl Iterator for Step
impl<I> Iterator for Step<I>
where I: Iterator {
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
let e = self.iter.next();
if self.skip > 0 {
self.iter.nth(self.skip - 1);
}
e
}
}
// interface / wrapper for Step
pub fn step<I>(iter: I, skip: usize) -> Step<I>
where I : Iterator,
{
assert!(skip != 0);
Step{
iter: iter,
skip: skip,
}
}
//impl step for all Iterator
pub trait IterExt : Iterator {
fn step(self, n: usize) -> Step<Self>
where Self: Sized,
{
step(self, n)
}
}
impl<T: ?Sized> IterExt for T where T: Iterator{}fn main() {
let mut ct = LimitCounter{start: 0, limit: 2};
println!("{}", ct.next().unwrap());
let arr = [1,2,3,4,5]; // iter(): yeild &T
// iter_mut(): yield &mut T
// into_iter(): Yeild &T, &mutT, T depends on the context let sum = arr.iter().step(1).fold(0, |acc, x| acc + x);
}
iter() family
- IntoIter: transfer the ownershop: &self
- Iter: get immutable reference: &self
- IterMut: get mutable reference: &mut self
iter() -> &T
iter_mut() -> &mut T
into_iter() -> T, &T, or &mut T depending on the context
struct Foo {
bar: Vec<u32>,
}
impl Foo {
fn all_zeros(&self) -> bool {
self.bar.into_iter().all(|x| x == 0) // can't do this because move semantic
}
iterator manipulation
// use inspect to check the item repsectively
let nvec = vec![1,2,3];
let dvec = nvec.iter()
.map(|i| i*2)
.inspect(|it| println!("the item is {}", it))
.collect<Vec<i32>>();
Trait as Abstract Type
Trait itself can be a type. However, the size of it can’t be defined during compile time. That is why we have to use pointer/reference when using trait objects.
trait Bar {
fn baz(&self);
}// must use ptr or reference since the size of the object is not defined during compile time
fn dynamic_dispatch(b: &Bar) {
t.baz()
}fn static_dispatch(b: impl Bar) {
b.baz()
}// trait like structure
// https://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/
pub struct TraitObject {
pub data: *mut (),
pub vtable: *mut (), // concept of virtual table from C++ which includes information of methods, destructors,...
}
Trait blanket
implement several structs for trait at once
trait Fooer: Copu + Clone + Ord + Bar {}
struct Foo<F: Fooer> {
vals: Vec<F>,
}
// implement ToString trait for all structs implementing Display and ?Sized
impl<T> ToString for T where
T: Display + ?Sized,
{ ... }