Question

Rust Orphan Rule: Implementing Traits for External Types with Newtypes

rusttraits

Question

How can I make an expression such as vec << 4 append 4 to a Vec in Rust? I attempted to implement the standard library's Shl trait for the standard library's Vec<T> type:

use std::ops::Shl;

impl<T> Shl<T> for Vec<T> {
    type Output = Vec<T>;

    fn shl(&self, elem: &T) -> Vec<T> {
        self.push(*elem);
        *self
    }
}

fn main() {
    let v = vec![1, 2, 3];
    v << 4;
}

However, Rust reports that an implementation cannot be provided when both the trait and the type are defined outside the current crate. Do I need to modify the standard library, or is there a way to make this compile successfully?

Short Answer

Rust's orphan rule prevents a crate from implementing an external trait, such as std::ops::Shl, for an external type, such as Vec<T>. The normal solution is to define a local wrapper type—called a newtype—around Vec<T>, then implement the operator trait for that wrapper. You will also learn when << and <<= are appropriate and how operator implementations consume or mutate values.

Concept

Rust uses traits to define shared behavior. For example, the << operator is defined by std::ops::Shl, and the <<= operator is defined by std::ops::ShlAssign.

Rust has a coherence rule commonly called the orphan rule:

You may implement a trait for a type only when your crate defines the trait, the type, or both.

Therefore, this is rejected:

use std::ops::Shl;

impl<T> Shl<T> for Vec<T> {
    // Not allowed: both Shl and Vec come from the standard library.
    type Output = Vec<T>;

    fn shl(self, rhs: T) -> Vec<T> {
        self
    }
}

Your crate owns neither Shl nor Vec<T>. If this were allowed, separate crates could define conflicting meanings for the same operation. One crate might make Vec << value append an item, while another might make it remove an item. Rust must guarantee that every trait/type combination has one unambiguous implementation.

You do not need to patch the standard library. Instead, create a type that your crate owns and wrap the vector inside it. Since the wrapper is local, you may implement standard traits for it.

There is also a separate issue in the attempted implementation: Shl normally receives self and the right-hand operand by value. Mutating a vector requires mutable access, and returning the original vector by value usually means consuming self. For an in-place operation, ShlAssign is usually the better operator trait.

Mental Model

Think of a trait implementation as a rule added to a shared rulebook.

  • Shl is a rulebook page owned by the Rust standard library.
  • Vec<T> is an object design owned by the Rust standard library.
  • Your crate cannot write a new rule that changes how two other owners' things interact.

A newtype is like putting the vector into a box with your own label:

struct PushVec<T>(Vec<T>);

You own PushVec<T>, so you can decide what PushVec << item means. The vector still stores the data, but your wrapper provides your crate's custom behavior.

Take Quiz

Syntax and Examples

Shl defines the << operator. Its simplified shape is:

use std::ops::Shl;

trait Shl<Rhs = Self> {
    type Output;

    fn shl(self, rhs: Rhs) -> Self::Output;
}

For a custom vector wrapper, << can consume the wrapper, append an item, and return the updated wrapper:

use std::ops::Shl;

#[derive(Debug)]
struct PushVec<T>(Vec<T>);

impl<T> Shl<T> for PushVec<T> {
    type Output = Self;

    fn shl(mut self, elem: T) -> Self {
        self.0.push(elem);
        self
    }
}

fn main() {
    let values = PushVec(vec![1, 2, 3]);
    let values = values << 4;

    println!("{:?}", values.0); // [1, 2, 3, 4]
}

The mut self parameter means the method owns a mutable local copy of the wrapper. It appends to the inner Vec<T> and returns the wrapper.

For syntax that directly communicates mutation, implement ShlAssign for <<= instead:

use std::ops::ShlAssign;

#[derive(Debug)]
struct PushVec<T>(Vec<T>);

impl<T> ShlAssign<T> for PushVec<T> {
    fn shl_assign(&mut self, elem: T) {
        self.0.push(elem);
    }
}

fn main() {
    let mut values = PushVec(vec![1, 2, 3]);
    values <<= 4;

    println!("{:?}", values.0); // [1, 2, 3, 4]
}

<<= is often a clearer choice because it modifies an existing value, much like +=.

Step by Step Execution

Consider this ShlAssign implementation:

use std::ops::ShlAssign;

struct PushVec<T>(Vec<T>);

impl<T> ShlAssign<T> for PushVec<T> {
    fn shl_assign(&mut self, elem: T) {
        self.0.push(elem);
    }
}

fn main() {
    let mut values = PushVec(vec![10, 20]);
    values <<= 30;
}

Execution proceeds as follows:

  1. values is created as PushVec(vec![10, 20]).

  2. values <<= 30 is operator syntax that Rust translates conceptually to:

    ShlAssign::shl_assign(&mut values, 30);
    
  3. The &mut values borrow gives shl_assign permission to modify values.

  4. self.0 accesses the wrapped Vec<T>.

  5. push(elem) adds 30 to that vector.

  6. After the statement, values.0 contains [10, 20, 30].

In contrast, values << 30 calls Shl::shl(values, 30). That form usually consumes values and produces a new result, which you should assign or otherwise use.

Real World Use Cases

The orphan rule and newtype pattern appear in many practical Rust programs:

  • Domain identifiers: Wrap String or u64 in types such as UserId, EmailAddress, or OrderId so unrelated values cannot be mixed accidentally.
  • Validated values: Wrap a String in NonEmptyName after verifying it is not empty.
  • Custom serialization: Wrap an external type when you need a custom serde representation but cannot directly implement an external serialization trait for that external type.
  • Units of measurement: Use Meters(f64) and Seconds(f64) to make invalid calculations harder to write.
  • Application-specific operators: A graphics, math, or query-builder library may use operator traits on local wrapper types when the meaning is intuitive to its users.
  • Collection APIs: A wrapper can expose restricted operations, logging, capacity limits, validation, or custom insertion behavior around a Vec<T>.
Take Quiz

Real Codebase Usage

In production Rust code, a newtype often provides more than a legal trait implementation. It creates a boundary where an application can control invariants and its public API.

A common pattern is to provide normal named methods in addition to any operator overload:

#[derive(Debug, Default)]
struct PushVec<T>(Vec<T>);

impl<T> PushVec<T> {
    fn new() -> Self {
        Self(Vec::new())
    }

    fn push(&mut self, value: T) {
        self.0.push(value);
    }

    fn into_inner(self) -> Vec<T> {
        self.0
    }
}

Named methods are usually clearer than unusual operator meanings. push immediately tells readers what happens; << may be surprising for a vector because Rust commonly uses it for bit shifting and stream-like APIs.

Developers also commonly implement conversion traits for ergonomic boundaries:

impl<T> From<Vec<T>> for PushVec<T> {
    fn from(values: Vec<T>) -> Self {
        Self(values)
    }
}

impl<T> From<PushVec<T>> for Vec<T> {
    fn from(values: PushVec<T>) -> Self {
        values.0
    }
}

Avoid automatically implementing Deref<Target = Vec<T>> unless the wrapper should truly behave like a vector everywhere. Deref can expose methods that bypass the rules or invariants the wrapper was meant to enforce.

Common Mistakes

Implementing an external trait for an external type

use std::ops::Shl;

impl<T> Shl<T> for Vec<T> {
    type Output = Vec<T>;

    fn shl(self, item: T) -> Vec<T> {
        self
    }
}

This fails due to the orphan rule. Define a local wrapper such as struct PushVec<T>(Vec<T>); and implement the trait for that wrapper.

Using the wrong Shl method signature

This does not match the Shl trait:

fn shl(&self, elem: &T) -> Vec<T> {
    todo!()
}

Shl<T> expects a method shaped like this:

fn shl(self, rhs: T) -> Self::Output

The exact types vary with your chosen right-hand side and output types, but the receiver is normally self, not &self.

Trying to mutate through &self

fn shl(&self, elem: T) -> Self {
    self.0.push(elem); // Error: cannot mutably borrow through &self
    todo!()
}

Use mut self for a consuming operator implementation, or use &mut self through ShlAssign for an in-place operator.

Moving out of a borrowed value

fn shl(&self, elem: &T) -> Vec<T> {
    self.push(*elem);
    *self
}

Even aside from the trait mismatch, this attempts to mutate through an immutable reference and move out of a borrowed value. It would also require T: Copy to use *elem. Prefer receiving the element by value when the operator should take ownership of it.

Ignoring the output of <<

let values = PushVec(vec![1, 2, 3]);
values << 4;

If Shl consumes and returns Self, the returned updated value must be used:

let values = values << 4;

If you want to keep and mutate the same binding, use <<= with a mutable variable.

Overloading operators with surprising meanings

Operator overloads should be easy to predict. If readers are likely to confuse << with a bit shift, a method such as push, append, or insert is usually more maintainable.

Comparisons

ChoiceSyntaxOwnership behaviorBest use
Vec::pushvalues.push(4)Mutably borrows the existing vectorThe standard, clearest way to append one item
Shllet values = values << 4Usually consumes the left value and returns an outputBuilder-style transformations where the operator meaning is clear
ShlAssignvalues <<= 4Mutably borrows and updates the left valueCustom in-place operator syntax
Newtype wrapperPushVec<T>(Vec<T>)Creates a distinct typeAdding trait implementations, validation, or domain meaning
Type aliastype PushVec<T> = Vec<T>Is exactly the same type as Vec<T>Shortening a type name; cannot bypass the orphan rule

A type alias does not create a new type, so it cannot solve this problem:

type PushVec<T> = Vec<T>;

Only a tuple struct, normal struct, or enum creates a genuinely local type that can receive the implementation.

Take Quiz

Cheat Sheet

  • Orphan rule: implement a trait only if your crate owns the trait or the type.

  • std::ops::Shl powers left << right.

  • std::ops::ShlAssign powers left <<= right.

  • You cannot write impl Shl<T> for Vec<T> in an ordinary application crate.

  • Create a local wrapper:

    struct PushVec<T>(Vec<T>);
    
  • Consuming << implementation:

    impl<T> std::ops::Shl<T> for PushVec<T> {
        type Output = Self;
    
        fn shl(mut self, rhs: T) -> Self {
            self.0.push(rhs);
            self
        }
    }
    
  • In-place <<= implementation:

    impl<T> std::ops::ShlAssign<T> for PushVec<T> {
        fn shl_assign(&mut self, rhs: T) {
            self.0.push(rhs);
        }
    }
    
  • Prefer Vec::push when custom operator syntax does not improve readability.

  • A type Alias = Vec<T> is not a new type and does not help.

FAQ

Why does Rust have the orphan rule?

It prevents conflicting trait implementations across crates. This lets Rust select implementations predictably and keeps generic code coherent.

Can I implement a standard library trait for my own type?

Yes. For example, you may implement std::ops::Shl for PushVec<T> because PushVec<T> is defined in your crate.

Can I implement my own trait for Vec<T>?

Yes. Your crate owns the trait, so this is allowed:

trait AppendOne<T> {
    fn append_one(&mut self, value: T);
}

impl<T> AppendOne<T> for Vec<T> {
    fn append_one(&mut self, value: T) {
        self.push(value);
    }
}

Why does Vec::push take &mut self but Shl::shl takes self?

They represent different API designs. push modifies an existing vector. Shl is designed for an expression that consumes operands and returns a result, though implementations can choose appropriate owned or referenced operand types.

Should I use << to append to a vector?

Usually, use push. It is conventional, immediately understandable, and already available. Use an overloaded operator only when it makes your domain API clearer.

Does a type alias bypass the orphan rule?

No. A type alias is another name for the same type. A tuple struct such as struct PushVec<T>(Vec<T>); creates a distinct type.

Can I add methods directly to Vec<T>?

No. Inherent impl blocks must also be written for types defined in your crate. Use an extension trait for additional methods or use a newtype for stronger control.

Take Quiz

Mini Project

Description

Build a small LogBuffer wrapper around Vec<String>. It demonstrates the newtype pattern and implements <<= as a convenient in-place operation for adding log messages. This resembles application code that collects events before displaying, saving, or sending them.

Goal

Create a local collection wrapper that accepts strings through <<= and can return its collected messages.

Requirements

Define a LogBuffer newtype that stores Vec<String>. Implement ShlAssign<String> for LogBuffer. Add a method that returns the number of stored messages. Add a method that consumes the buffer and returns the inner Vec<String>. In main, add at least two messages using <<= and print the count.

Take Quiz

Keep learning

Related questions

View more