Adapter Pattern in Rust: Overcoming the Orphan Rule with Newtype and Extension Traits

Wed, 06 May 2026 10:42:00 +0000
Rust, rust language, Programming Languages

Some time ago, we were talking about the Newtype pattern in the Rust programming language. For that pattern, we used a single-field tuple struct to either alter the inner type semantics or to hide the implementation type from the end user - often we wanted both.

This time, we will also take a closer look into the single-field tuple struct, this time to implement the adapter pattern. Let's start by stating the problem we want to solve with it.

Orphan Rule

I find the Rust typesystem to be one of the most difficult things to get a good grasp on - solely because it is very different from other object-oriented languages we are used to.

One of the unusual things is the relationship between Traits and user-defined types. Traditionally, traits are introduced as the implementation of interfaces. As they serve similar purposes - behavioral level abstraction - I personally am careful introducing traits as interfaces, as they have a significant difference. Typically, we think of interfaces that are bound to the type the moment the type is declared. So when we create the Orc type, we immediately define all interfaces that it implements - Creature, Foe, Mesh. That is not how traits work. Traits and types are completely orthogonal in the Rust language, defined independently. For public types, it is never possible to claim all the traits defined on the type - that set can always extend in dependent crates.

That, however, introduces a problem. Let's imagine that we have two crates: crate1 and crate2, both single lib.rs files:

// crate1/lib.rs

trait Applicator {
  fn apply(&self, a: u32) -> u32;
}

// crate2/lib.rs

struct Multiplicator {
  pub scale: u32,
}

The creator of crate2 has no clue of crate1's existence, nor the other way around. However there is a crate3 which author wants to use both crates, but needs implementation of Applicator on the Multiplicator type:

use crate1::Applicator;
use crate2::Multiplicator;

impl Applicator for Multiplicator {
  fn apply(&self, a: u32) -> u32 {
    self.scale * a
  }
}

This seems reasonable, and it's something we'd genuinely want to do. Bad news is: that code doesn't compile. Let's think of - why is it a problem?

Well, we will now introduce a crate4 that has a bit of a different understanding of how Applicator should be implemented on Multiplicator:

use crate1::Applicator;
use crate2::Multiplicator;

impl Applicator for Multiplicator {
  fn apply(&self, a: u32) -> u32 {
    a.pow(self.scale)
  }
}

Well - no problem, right? crate3 uses its own implementation of a trait, crate4 has its own, it is fine. However, things get tricky when we introduce crate5 that depends on all the mentioned crates so far:

use crate1::Applicator;

fn main() {
 let m = crate2::Multiplicator { scale: 3 };
 println!("{}", m.apply(3));
}

Now, there is a question: what does that crate5 print? 9 or 27? It's impossible to decide which implementation is correct here!

The answer could be - just fail to compile when that case occurs! crate5 could...