Traits
Traits Are Interface Types
Define methods, fields and types that must be present in the implementation of a struct or enum
Principal mechanism for code reuse in Rust
Interact strongly with and enhance "generic" types (parametric polymorphism)
Defining A Trait
Just an interface specification
Here's a hairy version
https://play.rust-lang.org/?gist=d0087cd38ededa59f70297615c676254
Note the following:
- Trait bounds
- Associated type
- Static (default) method
- Use of Sized marker type
- Use of Self
- Clever type inference
- Implementation of our trait for standard types
Trait Bounds vs Trait Objects
Book example
https://play.rust-lang.org/?gist=8aaa8fe367b1e495c0318d777bd0e83b
A specialized version of
generic_salad.add(*v)
will be constructed, no argument need be passed, the whole thing will be inlinedNote the awkwardness of working with trait objects. This code is fairly fragile
Misc Trait Things
Traits and scope control
Subtraits
Operator Overloading
This is an "easy, boring" chapter
There are traits that can be used to tell the Rust compiler to use standard operators for your standard type
Compromise between Nickle-style ("operator overloading makes programs unreadable") and C++-style ("overload any operator for any reason anytime")
What You Cannot (Must Not) Do
Cannot change precedence or associativity of standard operators
Overloaded operators are "expected" (not required, but really?) to obey basic arithmetic laws "as appropriate", for example
Associativity of
*
a * (b * c) == (a * b) * c
Transitivity of inequality
a < b < c ⇒ a < c
In general, it is good style to not be polymorphic with operators, e.g.
1.0 + 2
should maybe be rejected
What You Can Do
Pick any operator from the table in the chapter and specify its function on your own datatypes
https://play.rust-lang.org/?gist=c595ec69bf3e6522765b01f6a460a819
The arithmetic operators consume their arguments. Sorry. Usually derive
Copy
for arithmeticsCompound assignment operators are separate. They should be derivable, but currently aren't
Index
andIndexMut
allow overloading[]
in various contexts.IndexMut
requires producing a value, which is borked for types that want to do an initial assignment
Trait Garbage Bag
- There are a bunch of traits tied into Rust's internals or standard library with no real organizing principle
Clone
The
Clone
trait provides theclone()
andclone_from()
functionsclone_from()
is little-used: check out the implementation to understand one reason why.A
Clone
implementation should do a "deep copy"Clone
is usually derived, but see e.g.
Marker Traits
"Marker traits" are a communication channel between compiled code and the compiler
You can use a marker trait like any other trait
Marker traits can be ignored, e.g.
?Sized
Copy
Copy
is a marker trait that you implement when you want your values to be automatically copied by the compiler. It has no methodsCopy
providesClone
for freeUse
Copy
sparingly:- Makes the implementation be careful
- Expensive
- Semantics sometimes surprising
Drop
You can implement the
Drop
trait to get control of a value right before it is freedThis is used for e.g. closing files, flushing data, etc
A type implementing
Copy
cannot also implementDrop
, because the semantics are too confusing
Sized
Sized
is a marker trait that says that the compiler knows the size of values of the typeYou cannot implement
Sized
yourselfBy default, generic types implicitly require instantiation with something
Sized
You can turn this off with
+ ?Sized
("questionably sized") in situations where you don't want it
Deref, DerefMut
Used to transfer a
*
dereference of a type to some type in the inner structureCanonical cases are
Box
and similar containers
Default
Trait to provide a "default value" for your type
Be careful: you need to know how things are going to default to know what to expect.
AsRef, AsMut
Annoying Rust shorthand: "refmut" is usually just spelled "mut"
Traits for borrowing a reference to your type from a variety of values
Usually used for generic parameters. Book says
fn open<P: AsRef<Path>>(path: P) -> ... { let path = path.as_ref() ...
Borrow, BorrowMut
Identical to
AsRef
,AsMut
?!?By convention, implement for a type only when references are semi-interchangeable with values
Mostly for collection type convenience
This is where the types start to get truly ugly: see the book example
From, Into
Type conversion traits
Not really needed, but convenient convention
Into
is justFrom
with the types reversedNormally just implement
From
to get a default implementation ofInto
from()
andinto()
consume their argumentsfrom()
andinto()
can only fail bypanic()
;TryFrom
andTryInto
are in nightly
ToOwned
Trait for "cloning" a thing that implements the
Borrow
traitOne standard way to create a
String
from an&str
:"hello".to_owned()
Also works for slice to
Vec
Cow
"Copy On Write": keeps a reference until owned
Glory in the beauty of this: book says
enum Cow<'a, B: ?Sized + 'a> where B: ToOwned { Borrowed(&'a B), Owned(<B as ToOwned>::Owned), }
Observations
There's a lot of stuff here
Read this chapter carefully, then…
Return to it as you advance in Rust. The details are best appreciated in the context of a problem you are trying to solve
The result of this mess is a pretty expressive language: much is "hidden under the hood"