Traits: A Rusty Rabbit Hole - kvverti/rusty-lavender GitHub Wiki
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.
- Strong Typing: The language will never allow implicit conversions Ex: Python, Java
- 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
- Dynamic Typing: Language checks types and look for type errors during compile time. Ex: JavaScript, Python
- 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.
- You might notice that
pub
sounds like an abbreviation ofpublic
, like the access modifier in Java. In Rust,pub
is similar; it makes any module, function, or data structure accessible from inside external modules. - Next, you might notice that we are defining a trait called
Summary
. - 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 takesself
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 withself
.
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: