Things on this page are fragmentary and immature notes/thoughts of the author. Please read with your own judgement!
:timing
:sccache 1
Tips and Traps¶
Sometimes a trait bound might be too generic for your use case. If you want your function to take only a fixed number of types, make your function takes an Enum instead of a generic type.
If you define a trait with a bound, e.g.,
trait Derived : Base, avoid defining default method implmentations with the same name. That is if the traitBaseprovides a default implementationf, you should avoid providing another default implementationfforDerive. If you do provide default implementations with the same name in both traits, they are uncorrelated and can cause confusion easily. For more discussions, please refer to Rust Quiz 27.
Trait¶
Currently, a trait in Rust cannot access fields. If a default implementation is provided, the implementation cannot refer to any struct field.
Best Practices When Defining a Default Implementation for a Trait’s Method
Allow fields in traits that map to lvalues in impl'ing type #1546
Traits and trait objects - more than just interfaces - Rust Community Stuttgart
Traits That You Should Consider Implementing for Your Structs¶
Other Useful Traits¶
- Borrow
- AsRef
- num::traits::Unsigned
- num_traits::identities::Zero
PartialEq, Eq and Hash¶
- Unlike other popular programming languages,
Rust introduces the trait PartialEq
(for solving issues such as NaN in floating numbers).
The
Eqtrait is a trait without any method which indicating that an object of the struct implementingEqis comparable to itself. You always need to implement the traitPartialEqif you implementEq. This eventually means that if you implement any ofEqorHash, you need to implement all three ofPartialEq,EqandHash. One simple way to do this is to use the macro#[derive(PartialEq, Eq, Hash)]. However, you can implementPartialEqwithout implementingEqorHash.
Sized vs ?Sized¶
Sync vs !Sync¶
Copy vs Clone¶
Clone means the type can be duplicated. Copy means the type can be duplicated by copying bytes. This means that Copy implies Clone, so when you implements Copy you should always implement Clone.
Cloneis a common trait for the ability to explicitly duplicate an object. It differs fromCopyin thatCopyis implicit and extremely inexpensive, whileCloneis always explicit and may or may not be expensive. In order to enforce these characteristics, Rust does not allow you to reimplementCopy, but you may reimplementCloneand run arbitrary code. SinceCloneis more general thanCopy, you can automatically make anythingCopybeCloneas well.
- If a type does not implement the
Copytrait, it is moved when passed as a parameter. This might cause issues of "object moved". To resolve this issue, you have to implement theCopytrait. A simple way is to drive theCopyandClonabletraits using#[derive(Copy, Clone)].
AsRef¶
- The
AsReftrait is very useful to make a function taking a generic parameter of the typeTwhereTcan be converted into the reference of a type by calling the methodT.as_ref(). For example, ifCardis a struct and you'd like to implement a function which accepts both&Vec<Card>and&Vec<&Card>as the parameter, you can implement it as below.
fn id_sum<T>(cards: &Vec<T>) -> u64
where
T: AsRef<Card>,
{
cards.into_iter().map(|c| c.as_ref().id).sum()
}
A perhaps more useful example is AsRef<str>.
It is well known in Rust that
if you want to have a parameter accpeting a string,
it is best to specify its type as &str
as a String value can be converted to &str without copying.
fn print_str(s: &str) {
println!("{}", s);
}
print_str("How are you doing?");
let s: String = "How are you doing".into();
print_str(s.as_ref());
First,
the above example is not generic enough
as uses have to manually cast the type of value to &str.
Second,
what if we want to implement a function taking a vector of strings (&str, String, etc.)?
AsRef comes to rescue!
fn count_chars<T>(strs: &Vec<T>) -> usize
where
T: AsRef<str>
{
strs.iter().map(|s| s.as_ref().len()).sum()
}
let strs = vec!["how", "are", "you"];
count_chars(&strs)
let strs = vec!["how".to_string(), "are".to_string(), "you".to_string()];
count_chars(&strs)
{
let strs = vec!["how", "are", "you"];
let strs_ref = vec![&strs[0], &strs[1], &strs[2]];
count_chars(&strs_ref)
}
{
let strs = vec!["how".to_string(), "are".to_string(), "you".to_string()];
let strs_ref = vec![&strs[0], &strs[1], &strs[2]];
count_chars(&strs_ref)
}
AsRef vs Borrow¶
As you can see that the above function
accepts a vector of &str, String, &&str, &String,
and more.
IntoIterator - Trait for into_ter¶
When demonstraing the use of AsRef<T>,
we has a function taking a vector of values.
This is not generic enough.
For the same reason that &str is preferred over String as function parameters,
the slice type &[T] is preferred over &Vec<T>
(as a vector reference can be converted to a slice implicitly).
fn count_chars<T>(strs: &[T]) -> usize
where
T: AsRef<str>
{
strs.iter().map(|s| s.as_ref().len()).sum()
}
let strs = vec!["how", "are", "you"];
count_chars(&strs)
let strs = ["how", "are", "you"];
count_chars(&strs)
Pushing generic one step further,
we can make the above function taking a type implementing IntoIterator instead of &[T]
(similar to AsRef<str> vs &str).
This makes the function takes even more collection/iterator types
as long as they implement IntoInterator.
fn count_chars<I, T>(strs: I) -> usize
where
I: IntoIterator<Item = T>,
T: AsRef<str>
{
strs.into_iter().map(|s| s.as_ref().len()).sum()
}
let strs = vec!["how", "are", "you"];
count_chars(&strs)
let strs = vec!["how", "are", "you"];
count_chars(strs)
let strs = ["how", "are", "you"];
count_chars(&strs)
let strs = ["how", "are", "you"];
count_chars(strs)
Trait for iter¶
There is no Trait in Rust for iter
as it is not necessary
and can be achieve by calling into_iter on a reference type.
Examples of Generic Types with Trait Bounds¶
The following 2 examples are identical ways to specify trait bounds.
:dep num-traits = "0.2.14"
use num_traits::AsPrimitive;
fn sp1<T: AsPrimitive<usize>>(major_rank: T) -> f64 {
let r = major_rank.as_();
if r <= 5 {
return 0.0;
}
(r - 5) as f64
}
enum MyEnum {
A = 0,
B,
}
MyEnum::B as usize
MyEnum::B as i64
let x: i64 = MyEnum::B.into();
x
sp1(MyEnum::A)
sp1(6usize)
fn sp<T>(major_rank: T) -> f64 where T: AsPrimitive<usize> {
let r = major_rank.as_();
if r <= 5 {
return 0.0;
}
(r - 5) as f64
}
sp(6usize)
Super/Sub Trait and Generic Functions¶
RFC: Supertrait item shadowing #2845
trait Super {
fn foo(&self);
}
trait Sub: Super {
fn foo(&self);
}
impl Super for i32 {
fn foo(&self) {
println!("super");
}
}
impl Sub for i32 {
fn foo(&self) {
println!("sub");
}
}
fn super_generic_fn<S: Super>(x: S) {
x.foo();
}
fn sub_super_generic_fn<S: Sub>(x: S) {
generic_fn(x);
}
fn sub_generic_fn<S: Sub>(x: S) {
x.foo();
}
let x: i32 = 42;
x.foo()
let x: i32 = 42;
sub_generic_fn(x);
let x = 8u8;
let arr: [i64; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
(2..x as usize).map(|i| arr[i]).sum::<i64>()
(2..x).map(|i| arr[i as usize]).sum::<i64>()
Traits that You Probably Shouldn't Implement¶