Tour of Rust’s Standard Library Traits github.com/pretzelhamm… Praying for Rust
Contents ✅ ⏰
- The introduction ✅
- Trait based ✅
- Automatic Trait ✅
- Generic Trait ⏰ = > ✅
- Formatting Trait ⏰
- Operator Trait ⏰
- Conversion Trait ⏰
- Error handling ⏰
- The iterator traits ⏰
- The I/O Trait ⏰
- Conclusion ⏰
Generic traits
Default
Required knowledge
- Self
- Functions
- Derive Macros
trait Default {
fn default() - >Self;
}
Copy the code
You can construct defaults for types that implement Default.
struct Color {
r: u8,
g: u8,
b: u8,}impl Default for Color {
// default color is black
fn default() - >Self {
Color {
r: 0,
g: 0,
b: 0,}}}Copy the code
This is useful when prototyping quickly, especially if we don’t require too much and only need an instance of a type:
fn main() {
// just give me some color!
let color = Color::default();
}
Copy the code
When we want to explicitly expose the function to the user, we can also choose to do this:
struct Canvas;
enum Shape {
Circle,
Rectangle,
}
impl Canvas {
// let user optionally pass a color
fn paint(&mut self, shape: Shape, color: Option<Color>) {
// if no color is passed use the default color
let color = color.unwrap_or_default();
// etc}}Copy the code
Default is also useful in the generic context when we need to construct generic types:
fn guarantee_length<T: Default> (mut vec: Vec<T>, min_len: usize) - >Vec<T> {
for _ in 0..min_len.saturating_sub(vec.len()) {
vec.push(T::default());
}
vec
}
Copy the code
We can also initialize structural parts using the Default type in conjunction with Rust’s struct update syntax. Now we have a Color constructor new that takes all the members of the structure as arguments:
impl Color {
fn new(r: u8, g: u8, b: u8) - >Self {
Color {
r,
g,
b,
}
}
}
Copy the code
However, we can have more convenient constructors that each take only a portion of the structure’s members and use the default values for the rest of the structure’s members:
impl Color {
fn red(r: u8) - >Self{ Color { r, .. Color::default() } }fn green(g: u8) - >Self{ Color { g, .. Color::default() } }fn blue(b: u8) - >Self{ Color { b, .. Color::default() } } }Copy the code
There is also a Default derived macro that we can use to write Color like this:
// default color is still black
// because u8::default() == 0
#[derive(Default)]
struct Color {
r: u8,
g: u8,
b: u8
}
Copy the code
Clone
Required knowledge
- Self
- Methods (Methods)
- Default Impls
- Derive Macros
trait Clone {
fn clone(&self) - >Self;
// provided default impls
fn clone_from(&mut self, source: &Self);
}
Copy the code
We can convert immutable references of Clone type to the possessed value, namely &t ->T. Clone does not guarantee the efficiency of this conversion, so it can be slow and costly. We can quickly implement Clone on a type using derived macros:
#[derive(Clone)]
struct SomeType {
cloneable_member1: CloneableType1,
cloneable_member2: CloneableType2,
// etc
}
// macro generates impl below
impl Clone for SomeType {
fn clone(&self) - >Self {
SomeType {
cloneable_member1: self.cloneable_member1.clone(),
cloneable_member2: self.cloneable_member2.clone(),
// etc}}}Copy the code
Clone can be used to construct a type instance in a generic context. Here is an example taken from the previous section, where Default is replaced with Clone:
fn guarantee_length<T: Clone> (mut vec: Vec<T>, min_len: usize, fill_with: &T) -> Vec<T> {
for _ in 0..min_len.saturating_sub(vec.len()) {
vec.push(fill_with.clone());
}
vec
}
Copy the code
Clones are often used as an escape hatch to avoid borrowing checkers. Managing structures with references can be challenging, but we can clone references into owning values.
// oof, we gotta worry about lifetimes 😟
struct SomeStruct<'a> {
data: &'a Vec<u8>,}// now we're on easy street 😎
struct SomeStruct {
data: Vec<u8>,}Copy the code
If the program we’re writing isn’t performance-sensitive, then we don’t need to worry about cloning data. Rust is a language that exposes a lot of the underlying details, so it’s easy to get caught up in premature optimizations instead of really addressing the problem at hand. For many programs, the best order of priority is usually to build correctness first, elegance second, and performance third, only after performance has been dissected and performance bottlenecks identified. In general, this is good advice to follow, but you need to be aware that it may not apply to your program.
Copy
Required knowledge
- Marker Traits
- Subtraits & SuperTraits
- Derive Macros
trait Copy:Clone{}
Copy the code
We Copy the Copy type, for example: T-> t. Copy promises that the Copy operation is simple bitwise Copy, so it is fast and efficient. We can’t implement Copy ourselves, only the compiler can provide it, but we can get the compiler to do so by using the Copy derived macro, just as we did with the Clone derived macro, because Copy is a Clone subtrait:
#[derive(Copy, Clone)]
struct SomeType;
Copy the code
Copy refines Clone. A Clone operation can be slow and expensive, but a copy operation is guaranteed to be fast and inexpensive, so a copy is a faster clone operation. If a type implements Copy, the Clone implementation is irrelevant:
// this is what the derive macro generates
impl<T: Copy> Clone for T {
// the clone method becomes just a copy
fn clone(&self) - >Self{*self}}Copy the code
When a type implements Copy, its behavior changes when it is moved. By default, all types have move semantics, but once a type implements Copy, it has Copy semantics. To explain the difference, let’s look at these simple scenarios:
// a "move", src: ! Copy
let dest = src;
// a "copy", src: Copy
let dest = src;
Copy the code
In both cases, dest = SRC copies the contents of SRC bitwise and moves the result to dest. The only difference is that, in the first case (“a move”), the borrowing inspector invalidates the SRC variable and ensures that it is not used anywhere else later; In the second case (“a copy”), SRC is still valid and available.
In short: Copy is move, and move is copy. The only difference between them is the way they treat the borrowed inspector.
For a more concrete example of a move, assume that SEC is a Vec
type and its contents look like this:
{ data: *mut [i32], length: usize, capacity: usize }
Copy the code
When we execute dest = SRC, we get:
src = { data: *mut [i32], length: usize, capacity: usize }
dest = { data: *mut [i32], length: usize, capacity: usize }
Copy the code
In this unknown case, SRC and dest each have a variable reference alias for the same data, which is a big no-no, so the borrow checker invalidates the SRC variable without the compiler reporting an error. So that it can no longer be used.
For a more specific copy example, suppose SRC is an Option
and its contents look like this:
{ is_valid: bool, data: i32 }
Copy the code
Now, when we execute dest = SRC, we get:
src = { is_valid: bool, data: i32 }
dest = { is_valid: bool, data: i32 }
Copy the code
Both of them are available at the same time! Therefore, Option
is Copy.
Although Copy is an automatic trait, the designers of the Rust language decided to let types explicitly choose Copy semantics rather than silently inherit Copy semantics when types meet their conditions, which can lead to messy behavior that often leads to bugs.
Any
Required knowledge
- Self
- Generic Blanket Impls
- Subtraits & Supertraits
- Trait Objects
trait Any: 'static {
fn type_id(&self) -> TypeId;
}
Copy the code
Rust’s polymorphic style is parameterized, but if we are trying to use a more ad-hoc polymorphic style similar to a dynamically typed language, we can simulate it by using the Any trait. We don’t have to implement the Any trait for our type manually because this is covered by the Generic Blanket ImpL:
impl<T: 'static+?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}
Copy the code
We extract a T from a dyn Any using the downcast_ref::
() and downcast_mut::
() methods:
use std::any::Any;
#[derive(Default)]
struct Point {
x: i32,
y: i32,}impl Point {
fn inc(&mut self) {
self.x += 1;
self.y += 1; }}fn map_any(mut any: Box<dyn Any>) -> Box<dyn Any> {
if let Some(num) = any.downcast_mut::<i32>() {
*num += 1;
} else if let Some(string) = any.downcast_mut::<String>() {
*string += "!";
} else if let Some(point) = any.downcast_mut::<Point>() {
point.inc();
}
any
}
fn main() {
let mut vec: Vec<Box<dyn Any>> = vec![
Box::new(0),
Box::new(String::from("a")),
Box::new(Point::default()),
];
// vec = [0, "a", Point { x: 0, y: 0 }]
vec = vec.into_iter().map(map_any).collect();
// vec = [1, "a!", Point { x: 1, y: 1 }]
}
Copy the code
This trait is rarely needed because, in most cases, parameterized polymorphism is preferable to temporary polymorphism, which can also be modeled with enumerations (enums), which have better type safety and require less indirection (abstraction). For example, we could implement the above example as follows:
#[derive(Default)]
struct Point {
x: i32,
y: i32,}impl Point {
fn inc(&mut self) {
self.x += 1;
self.y += 1; }}enum Stuff {
Integer(i32),
String(String),
Point(Point),
}
fn map_stuff(mut stuff: Stuff) -> Stuff {
match &mut stuff {
Stuff::Integer(num) => *num += 1,
Stuff::String(string) => *string += "!",
Stuff::Point(point) => point.inc(),
}
stuff
}
fn main() {
let mut vec = vec![
Stuff::Integer(0),
Stuff::String(String::from("a")),
Stuff::Point(Point::default()),
];
// vec = [0, "a", Point { x: 0, y: 0 }]
vec = vec.into_iter().map(map_stuff).collect();
// vec = [1, "a!", Point { x: 1, y: 1 }]
}
Copy the code
Although Any is rarely needed, it can be handy in some cases, as we’ll see in the Error Handling section below.