Compose Instances Right Where They Are Needed with Rust Traits

a graphic depicting a package being 'built' and inserted into a set of other packages

With Rust Traits, it’s possible to compose Instances (essentially objects if you’re coming from the OOP world) right where they are needed. This makes it possible to tell more of the story about what the intent of the code is via Traits. Keeping traits simple, and authoring rich trait names describes in place the intent of what the code should be doing. Let’s get started.

A struct must already exist

So let’s take one from my PicoPonics project that is used for menu navigation and selection handling.

For more information on what the MenuNode is meant to do, please check out my series on creating an LCD Menu using the SSD1306 LCD and the Raspberry Pi Pico. In the interest of keeping this succinct I’ll omit that information from this post.

Traits Give Struct Instances Meaning

a graphic of an entity looking at a set of 'traits' that mostly clearly describe what they are going to do for that struct

Convey intent to future you, with concise, meaningfully named traits

Continuing with the menu page example, let’s say there is need for a menu page that will display “Hello, Alex”.

It’s possible to use our existing MenuNode struct and create or import the traits that we find most relevant to achieve the results we need.

After defining and implementing the trait GreetingNode for the MenuNode, when MenuNode::new() is invoked, it will create a new instance of MenuNode using the implemented methods defined in GreetingNode.

This is great because now we can use the associated MenuNode data however we want, in a way that doesn’t leak out of the currently active context, which would be this specific menu page.

Here is a unit test showing it in action:

That’s Composability!

That’s really all there is to it. Any other functionality that is specific to the GreetingNode can be added to the trait and then implemented. If the trait that’s being added is widely useful, it can be abstracted away and implemented for MenuNode in the same context where GreetingNode is implemented.

But Most importantly, The work done on GreetingNode will not have any impact on any other Node. That’s so useful, especially in the case of a menu where ideally there aren’t many redundant features being delivered across pages. So every page will need to have some unique aspects while having some uniform functionality, and this is perfect for such cases.

Previous
Previous

What is a Default Trait Implementation in Rust?

Next
Next

LCD Menu: Link UI Selections to Application State