What typical scenario would traits be used for in a Rust program? I have tried to wrap my head around them, but I still don't have a clear concept as to why one would decide to use them. I understand the syntax from reading about them from here.
How would one explain them to someone as if they were 5?
One way to conceptualize traits is to think of them as 'behaviors you want available to, and to operate over, a particular chunk of state'. So, if you have some struct that contains state, and you want to do something with it, you would write a trait for it.
There are two primary usages:
In the first case, it allows you to do things like this:
struct Article {
body: String
}
trait Saveable {
fn save(&self) -> ();
}
impl Saveable for Article {
fn save(&self) -> () {
... // All the code you need to run to save the Article object
}
}
// A function called by your UX
fn handle_article_update(article: Article) -> () {
...
article.save() // Call the save functionality
}
The second case is arguably more interesting, though. Let's say you - or more probably a third party - has a function defined like this:
fn save_object(obj: Saveable) -> () {
...
obj.save()
}
struct Person {
name: String
}
impl Saveable for Person {
fn save(&self) -> () {
... // Code needed to save a Person object, could be different from that needed for an Article object
}
}
...
// Note that we are using the same function to save both of these, despite being different underlying Structs
save_object(article)
save_object(person)
What this means is that the save_object
function does not need to know anything about your custom Article
struct in order to call save
on it. It can simply refer to your implementation of that method in order to do so. In this way you can write custom objects that third party or generic library functions are able to act upon, because the Trait
has defined a set of behaviors with a contract that the code can rely on safely.
Then the question of, 'when do you want to use a Trait' can be answered by saying: whenever you want to use behavior defined on a struct without needing to know everything about the struct - just that it has that behavior. So, in the above, you might also have an 'edit' functionality attached to Article
, but not to Person
. You don't want to have to change save_object
to account for this, or to even care. All save_object
needs to know is that the functions defined in the Saveable
trait are implemented - it doesn't need to know anything else about the object to function equally well.
Another way to phrase this is to say, 'Use a trait when you want to pass an object based on what it can do, not what it is.'