Newtype or newtrait? Ways around the orphan rule
2024-10-203 minute read
I’m currently working on a codebase using two crates exposing a very similar enum Priority
, representing some sort of priority level, say “LOW”, “MEDIUM”, “HIGH” and “CRITICAL”.
I want to convert these enums into a u8
, but the orphan rule prohibits me from implementing Into<u8>
for these enums.
I've read about the ✨ newtype pattern ✨ as a solution to the orphan rule.
use external_crate::Priority;
// This is what we refer to as the "newtype"
struct PriorityWrapper(external_crate::Priority);
// We can now implement the foreign trait Into for our _local_ type PriorityWrapper
impl Into<u8> for PriorityWrapper {
fn into(self) -> u8 {
match self.0 {
Priority::LOW => 1,
Priority::MEDIUM => 2,
Priority::HIGH => 3,
Priority::CRITICAL => 4,
}
}
}
// Example usage
fn print_a_priority(priority: PriorityWrapper) {
let number: u8 = priority.into();
println!("Priority as number: {}", number);
}
Although I got around the orphan rule using this pattern, I was not entirely satisfied with the solution.
As an alternative to using the newtype pattern, I've experimented with creating a new trait instead: PriorityExt
. Then I could implement this trait for the external enums. This way, I still use the underlying type, but I also have a new method convert_to_u8()
available.
use external_crate::Priority;
// This is not a "newtype", but a new trait instead!
trait PriorityExt {
fn convert_to_u8(&self) -> u8;
}
// It is quite similar to the Into<T> trait
impl PriorityExt for external_crate::Priority {
fn convert_to_u8(&self) -> u8 {
match self {
Priority::LOW => 1,
Priority::MEDIUM => 2,
Priority::HIGH => 3,
Priority::CRITICAL => 4,
}
}
}
// Example usage
fn print_a_priority(priority: Priority) {
let number: u8 = priority.convert_to_u8();
println!("Priority as number: {}", number);
}
Now do we call this “newtrait pattern” or what?
No we don’t.
I just jumped into the Rust Discord and got informed that this technique is more commonly known as “extension traits”. Although not mentioned in the Rust Book, it seems this pattern has been gaining popularity and being mentioned in the Rust RFC book and on Karol Kuczmarski’s blog.
Comparing extension traits to the newtype pattern
Why would you use extension traits instead of the newtype pattern to work around the orphan rule?
One great benefit from using extension traits is the improved developer experience compared to newtypes
- You would not be mistaking the newtype from the underlying type
- No need to do difficult naming of the wrapping newtype
- No need to re-expose the methods on the underlying type
One disadvantage of extension traits is that you have to import the trait to be able to use it. The language server suggests this for you however, with automatic code patching as well, making it a non-issue for those using a modern IDE.
Why am I writing this blog post?
Well, I was searching for a solution to the orphan rule, and nowhere was extension traits mentioned as a solution. A quick search for “rust orphan rule traits” yields no results mentioning extension traits (at the time of writing).
The Rust Book only highlights the newtype pattern as a solution to the orphan rule in the chapter about Advanced Types. And Karol’s blog post compares the language feature to C#.
But extension traits for me has been an elegant workaround for the orphan rule!
Well, now it’s out there! 💁♂️
Other solutions 💡
Why use a trait at all? This could be a function
For sure! Using a function for a simple type conversion would be perfectly fine. But if you’re working with multiple “similar” foreign types, a trait could help align those behind a common API.
This would also not give you any code completion on the foreign type when hitting “.”, making the solution a bit more hidden for future developers.
Hey, I'm Magnus, a developer from Norway.
I'm currently employed at Fink AS.
Hey, I'm Magnus, a developer from Norway.
I'm currently employed at Fink AS.
I also write about technical stuff in general
I made an AI chatbot answering questions for employees at our company
2023-10-219 minute read
In this article, I'll explain to you how I used OpenAI embeddings and completions API to implement an AI chatbot answering questions for employees at our company. The code examples I'll show are written in Typescript, but the same principles apply to any programming language.
Kunstig Humor – Improv theater meets AI
2023-10-184 minute read
This fall, I've been assisting the comedy group Vrøvl in setting up a show where improvisers and the audience interact with AI live on stage.
My first stab at 3D game development
2023-08-233 minute read
I wanted to learn about 3D game development, so I set out on a small project inspired by a friend of mine who lives on a farm. His name is Gunnar and he lives on a farm called Steinseth Gård.
How most Rust projects are organized (Part 2)
2022-01-102 minute read
In part 2, I manually inspected a selection of Rust projects looking for patterns in how files and folders usually are structured.
How most Rust projects are organized
2022-01-072 minute read
I collected data from GitHub.com to see what resides in most Rust projects src
folder.
I implemented Twitter in the woods using military radios
2018-02-0415 minute read
In this project I implemented a very simple Twitter-like application for use in networks with very low bandwidth and high packet loss rate.