gear idea
Possible Rust

Learning what’s possible in Rust.
Jump to Navigation

Conditional Impls

Mar 13th, 2026 · Pattern · #types · By Andrew Lilley Brinker

It’s possible in Rust to conditionally implement methods and traits based on the traits implemented by a type’s own type parameters. While this is used extensively in Rust’s standard library, it’s not necessarily obvious that this is possible.

In this article I’ll break down what the pattern is, give some examples of its use in the standard library to show why it’s valuable, and explain when you might want to use it.

What is a Conditional Impl?

A conditional impl is when a type implements methods or traits only if its own type parameters implement specific prerequisite traits. For methods, it looks like this:

Skip this content.
struct MyStruct<T> {
    inner: T,
}

impl<T> MyStruct<T> {
    fn always_available_method(&self) {
        // This method is always available, regardless of what
        // traits `T` implements.
    }
}

impl<T: Clone> MyStruct<T> {
    fn only_when_clone_is_implemented(&self) -> T {
        // This method is only available when `T` implements `Clone`.
    }
}
Code 1

A conditionally implemented method.

For those new to Rust, you may be surprised that having multiple impl blocks for a single type is valid. It is absolutely valid to do this!

In this example, the type MyStruct has a type parameter, T. The first impl block, the kind you’re likely familiar with, is an unconditional impl block, meaning that the methods it defines are always available on MyStruct.

The second impl block is a conditional impl block, meaning that the methods it defines are only available on MyStruct when T implements Clone.

You can also do something similar with conditional trait implementations. For example:

Skip this content.
struct MyStruct<T> {
    inner: T,
}

impl<T: Clone> Clone for MyStruct<T> {
    fn clone(&self) -> Self {
        Self { inner: self.inner.clone() }
    }
}
Code 2

A conditionally implemented trait.

In this example, MyStruct conditionally implements Clone only when T implements Clone.

So, we now understand what a conditional impl is, but why would you want to do this?

Why are Conditional Impls Useful?

Put simply: conditional impls let you extend the powers afforded to a type in specific situations. To understand better, let’s look at some examples from the Rust standard library.

The Cell type

The Cell type is one of Rust’s container types that provides “interior mutability.” Interior mutability is the property that you can still mutate the value inside of the container when you only have an immutable reference to the container itself. Put simply, the following is valid with Cell:

Skip this content.
use std::cell::Cell;

fn modify_cell(cell: &Cell<i32>) {
    cell.set(10);
}

fn main() {
    let cell = Cell::new(5);
    // Notice, we only pass a `&` reference, not a `&mut` reference.
    modify_cell(&cell);
    println!("{}", cell.get());
    // Prints: 10
}
Code 3

Interior mutability with Cell.

As you can see, modify_cell takes only an immutable reference to Cell<i32>, and yet it is able to mutate the value inside of the Cell with the method Cell::set. That’s the power of interior mutability.

So why is this safe? Well, Cell does not permit direct reference access to the inner value, so all access to that inner value is mediated through Cell’s API. For all T, Cell offers three methods for accessing the inner value:

  • replace: Replaces the inner value with a new value, returning the old value.
  • into_inner: Returns the inner value, consuming the Cell.
  • set: Sets the inner value to a new value, dropping the old value.

Rust’s guarantee is “aliasing XOR mutability,” meaning you can have either multiple references to a single value or you can have that value be mutable, but not both at the same time. With Cell, having an immutable reference to the Cell does let you mutate its interior value, but only in ways that either replace the value (either returning or dropping the old value), or destroying the Cell entirely, at which point you lose the interior mutability.

Even better, Cell uses conditional impls to provide more API options depending on the traits implements by the inner type!

If the inner type implements Copy, Cell::get returns a copy of the inner value, leaving the Cell itself unchanged. This is safe for copyable types because the copy is independent of the original value, so there’s no aliasing introduced.

If the inner type implements Default, Cell::take returns the inner value, replacing it with the default value for that inner type! Again, this is safe because it doesn’t permit aliasing of the inner type, instead moving it out and replacing it with a new default value.

Cell has even more conditional impls than these, including my personal favorites: the cell “projection” methods! If you have a Cell that contains a slice ([T]) or array ([T; N] where N is the size of the array), you get methods that distribute the Cell over the slice or array elements.

For slices, you have Cell::as_slice_of_cells, which converts a &Cell<[T]> into a &[Cell<T>].

For arrays, you have Cell::as_array_of_cells, which converts a &Cell<[T; N]> into a &[Cell<T>; N].

These are valid because the Cell type is guaranteed to be the same size as the value it contains since there’s no runtime bookkeeping involved in Cell’s API, so it doesn’t actually store any extra metadata about the inner value.

With all of these APIs, Cell becomes more powerful depending on what is stored in it.

The Clone derive macro

The conditional impl pattern also appears in some common derive macros, including the Clone derive macro.

As you’re likely aware, Clone is a derive macro that generates an impl block for the Clone trait for the type to which it is applied. It looks like this:

Skip this content.
#[derive(Clone)]
struct MyStruct {
    inner: i32,
}
Code 4

The Clone derive macro.

However, when the annotated type takes type parameters, the Clone derive macro generates a conditional impl block, which is dependent on those type parameters themselves implementing Clone.

This is a design choice in the Clone derive macro to make it maximally useful. It could instead produce an error on types with type parameters, or it could generate an unconditional Clone trait impl that would fail to compile when type parameters do not implement Clone, but this would be overly restrictive for a derive macro provided in the standard library.

With that said, this is what that conditional impl block (pretty much) looks like in practice:

For the following code:

Skip this content.
#[derive(Clone)]
struct MyType<T> {
    inner: T,
}
Code 5

The Clone derive macro with type parameters.

The Clone derive macro generates the following conditional impl block:

Skip this content.
impl<T: Clone> Clone for MyType<T> {
    fn clone(&self) -> Self {
        MyType { inner: self.inner.clone() }
    }
}
Code 6

The Clone derive macro’s conditional impl block.

While this is usually what you want when using the Clone derive macro, there are contexts when this will not work as expected. In particular, there are types which take type parameters which are not reflected in any stored value in the type (meaning they only exist at compile time), and which therefore should not be considered when implementing Clone.

The Clone derive macro is not able to identify what type parameters are relevant vs. irrelevant, and so in these cases it will generate a conditional impl block such that the type will not implement Clone even when all of its relevant type parameters implement Clone.

I’ve encountered this myself in the writing of the omnibor crate. OmniBOR is a specification for reproducible identifiers for software artifacts, and the omnibor crate provides a type called ArtifactId for that identifier.

This ArtifactId is a wrapper around a buffer which stores the result of hashing the target artifact as a Git blob object. Since the OmniBOR specification permits multiple hash algorithms for forward compatibility (although only SHA-256 may be used today), the ArtifactId type takes a type parameter which reflects the hash algorithm used. ArtifactId is more accurately ArtifactId<H: HashAlgorithm>.

When writing this API, I’d initially used the Clone derive macro to implement Clone for ArtifactId. However, this made the implementation of the Clone trait conditional on whether H implemented Clone, which it does not.

The solution here was to not use the Clone derive macro, and instead to write my own Clone implementation which properly ignored the H type parameter.

Conclusion

As you can see, conditional impls are a useful pattern for extending the powers of a type based on its type parameters. Their use in the standard library results in container types which gain new APIs based on the operations available on the types they contain, and in derive macros which are maximally flexible and useful for their users.

While this pattern will not suit every situation, I recommend keeping it in mind when designing your own types and APIs in the future.

Andrew Lilley Brinker


Andrew works on software supply chain security by day and writes educational Rust posts by night. He started Possible Rust in 2020 and can usually be found on Bluesky.

Possible Rust succeeds when more people can join in learning about Rust! Please take a moment to share, especially if you have questions or if you disagree!

Share on Bluesky

Discussions