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 traitBase
provides a default implementationf
, you should avoid providing another default implementationf
forDerive
. 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
Eq
trait is a trait without any method which indicating that an object of the struct implementingEq
is comparable to itself. You always need to implement the traitPartialEq
if you implementEq
. This eventually means that if you implement any ofEq
orHash
, you need to implement all three ofPartialEq
,Eq
andHash
. One simple way to do this is to use the macro#[derive(PartialEq, Eq, Hash)]
. However, you can implementPartialEq
without implementingEq
orHash
.
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.
Clone
is a common trait for the ability to explicitly duplicate an object. It differs fromCopy
in thatCopy
is implicit and extremely inexpensive, whileClone
is 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 reimplementClone
and run arbitrary code. SinceClone
is more general thanCopy
, you can automatically make anythingCopy
beClone
as well.
- If a type does not implement the
Copy
trait, it is moved when passed as a parameter. This might cause issues of "object moved". To resolve this issue, you have to implement theCopy
trait. A simple way is to drive theCopy
andClonable
traits using#[derive(Copy, Clone)]
.
AsRef¶
- The
AsRef
trait is very useful to make a function taking a generic parameter of the typeT
whereT
can be converted into the reference of a type by calling the methodT.as_ref()
. For example, ifCard
is 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¶