Skip to content

Traits: A Rusty Rabbit Hole

ProLeopard edited this page Dec 4, 2020 · 11 revisions

What Are Traits in Rust?

Traits are similar to interfaces in other languages, but have a few differences. They help define shared behavior amongst types. Essentially, a trait describes an abstract interface that types can implement. Let's quickly talk about what types are.

Types

Every variable, item, and value in a Rust program has a type. Some programming languages are typed (or strongly typed), while others are untyped (or weakly typed).

The third (current) iteration of Lavender is a strongly typed language, while prior iterations (v1 and v2) were weakly typed.

A few examples of types in Rust are as follows:

  • Primitive Types
    • bool
    • int
    • float
  • User Defined Types
    • struct
    • enum
  • Trait Types
    • impl

Strong, Weak, Static, and Dynamic Typing

Strong and Weak Typing refer to how strict the typing of a language is. Static and Dynamic Typing refer to when typing occurs.

  1. Strong Typing: The language will never allow implicit conversions Ex: Python, Java
  2. Weak Typing: The language may sometimes allow implicit conversions Ex: JavaScript, C/C++

For a little bit more information, visit: https://www.destroyallsoftware.com/compendium/strong-and-weak-typing?share_key=6b0dd1ec18ab6102

  1. Dynamic Typing: Language checks types and look for type errors during compile time. Ex: JavaScript, Python
  2. Static Typing: Language checks types and looks for type errors during runtime. Ex: C/C++, Java

Languages can be either strongly or weakly typed AND dynamically or statically typed. Lavender, just like Haskell, is strongly and statically typed.

We will quickly expand on statically typed languages because that is what is relevant for Lavender. The main advantage of a statically typed language is that all type errors are detected at compile time. This will help the user prevent mistakes and allows the compiler to generate more efficient machine code. In Haskell, for example, attempting to evaluate the expression 5 + 'b' will result in a compile time error as the expression is ill-typed. In JavaScript, a weakly and dynamically typed language, the expression will compile and the result will be 5b. In addition, user defined type signatures are also required to be correct.

Back to Traits

Now that we have some understanding of what types are, let's go back to traits. A type's behavior is defined by the methods that we can call on that type. Different types share the same behavior if we are able to call the same methods on all of those types.

Trait definitions are a way to group method signatures and define a set of behaviors.

Defining and Implementing Traits (Examples)

We will use the example given by the Rust Book to understand traits, but we will explain it a little bit differently.

Let us create a trait called Summary that defines a function summarize: pub trait Summary {fn summarize(&self) -> String;} There are a couple of things to unpack here.

  1. You might notice that pub sounds like an abbreviation of public, like the access modifier in Java. In Rust, pub is similar; it makes any module, function, or data structure accessible from inside external modules.
  2. Next, you might notice that we are defining a trait called Summary.
  3. Now, we are defining a function called summarize that takes a parameter &self and returns a String type. The most unfamiliar thing here might be the &self, which takes self as a shared reference, which cannot be mutated within the body of an object. self can also be taken as a value or as a mutable reference. We will run through an example of how this trait would be implemented soon, so you will have a more concrete understanding of what is happening with self.

Assume we now create a struct called NewsArticle that has a few Strings: headline, location, author, content. We can now implement the Summary trait we created earlier on the NewsArticle struct:

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

Let us go through this code line by line. Line 1: We use the impl keyword to implement the Summary trait for the NewsArticle struct. Line 2: We must now provide our own custom behavior of the summarize function. Here, we declare the function signature. Line 3: Now, we provide our custom behavior. format! is a Rust macro (not a function) that concatenates strings. For example, if headline, author, and location were "Rust," "Joe," and "New York," respectively, the result of this macro would be "Rust, by Joe (New York)".

The above is just a simple example of how Traits can be used in Rust. Traits are used in many parts of Lavender's code!

This information was primarily sourced from the following:

  1. https://doc.rust-lang.org/book/ch10-02-traits.html
  2. https://doc.rust-lang.org/reference/items/traits.html