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

Trait Bounds vs Trait Objects

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 arithmetics

  • Compound assignment operators are separate. They should be derivable, but currently aren't

  • Index and IndexMut 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 the clone() and clone_from() functions

  • clone_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.

    http://github.com/BartMassey/sivec

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 methods

  • Copy provides Clone for free

  • Use 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 freed

  • This is used for e.g. closing files, flushing data, etc

  • A type implementing Copy cannot also implement Drop, because the semantics are too confusing

Sized

  • Sized is a marker trait that says that the compiler knows the size of values of the type

  • You cannot implement Sized yourself

  • By 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 structure

  • Canonical 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 just From with the types reversed

  • Normally just implement From to get a default implementation of Into

  • from() and into() consume their arguments

  • from() and into() can only fail by panic(); TryFrom and TryInto are in nightly

ToOwned

  • Trait for "cloning" a thing that implements the Borrow trait

  • One 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"

Last modified: Wednesday, 17 July 2019, 2:13 PM