Fancy Traits
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_into()
functionsclone_into()
is a good idea but little-usedA
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
Probably a bad idea: what does "default value" even mean?
Book provides a sketchy use case
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"