Self in Traits

Understand the Self keyword inside Rust traits, its two distinct roles, default Sized bounds, and how it affects object safety and associated items.

The keyword Self appears in almost every Rust trait, but it operates on two different levels that are easy to conflate. Inside a trait definition, Self stands for the concrete type that will eventually implement the trait — a placeholder the compiler fills in later. In a method signature, self (lowercase) is shorthand for a value of that type. Getting these two roles straight is the key to writing traits that work with generics, trait objects, and default implementations without surprises.

Self vs self — The Two Roles

The capitalisation is the only visual difference, but the meaning is entirely separate:

  • Self is a type. It refers to the implementing type itself, wherever that name is needed in a trait's signature, associated types, or default method bodies.
  • self is a value. It is the receiver parameter of a method and is always shorthand for some form of self: Self. To see this in practice, imagine a trait that builds a String representation of something:
trait Describe {
    fn describe(&self) -> String;
}

The &self parameter is equivalent to self: &Self. The compiler reads self (the value) and knows it must be a reference to Self (the type). This shorthand works for three common receiver patterns:

trait Describe {
    fn consume(self) -> String;          // self: Self
    fn borrow(&self) -> String;          // self: &Self
    fn borrow_mut(&mut self) -> String;  // self: &mut Self
}

Receiver syntax is exclusively for method parameters:

Only the first parameter of a method (the receiver) can use the self shorthand. All other parameters must be written with explicit types. You cannot write fn foo(self, other_self: Self) — that's fine, but the first self is the receiver shorthand, while the second parameter uses the type keyword Self.

Beginners often assume Self is just a nicer way of spelling the struct name, but inside a trait it carries no information about the concrete layout. That's exactly why it's useful: a trait can describe behavior for many types without naming any of them.

Self in Trait Definitions

Every use of Self in a trait's method signatures and associated type declarations acts as a placeholder. The standard library's Clone trait is a minimal, instructive example:

pub trait Clone {
    fn clone(&self) -> Self;

    fn clone_from(&mut self, source: &Self) {
        *self = source.clone();
    }
}

Here, clone returns Self — the concrete type that implemented Clone. For String, Self resolves to String. For Vec<u32>, it resolves to Vec<u32>. The method signature says: "Whatever type you are, give me back another one of you." The second method, clone_from, takes &Self as its second parameter, so the source must be of the same concrete type. This is a pattern you will see repeatedly: Self in parameters and return positions ensures that operations stay within the same type family without naming the type. Associated types also use Self to refer to the implementing type's own associated items. The Iterator trait is a classic example:

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

Self::Item means "the Item type chosen by whichever concrete type implements Iterator." For std::vec::IntoIter<i32>, Self::Item is i32; for std::str::Chars, Self::Item is char.

You can read Self as 'my type' when implementing:

When you write impl MyTrait for MyType, every Self inside that impl block means MyType. The compiler substitutes it automatically, which is why you can write method bodies without ever typing the struct name again.

Self in Default Implementations

Traits can supply default method bodies, and those bodies can use Self freely. The Clone::clone_from method above is one example. Another is a trait that prints a debug summary when Debug is available:

use std::fmt::Debug;

trait Summary {
    fn summarize(&self) -> String;
}

trait DebugSummary: Debug {
    fn debug_summarize(&self) -> String {
        format!("{:?}", self)  // self is &Self
    }
}

The default body calls format!("{:?}", self), which works only because DebugSummary requires Debug as a supertrait. The self parameter is &Self, and Self: Debug is guaranteed. A more subtle case appears when a default method returns Self:

trait Default {
    fn default() -> Self;
}

This method has no self parameter — it is an associated function, not a method. The return type is Self. When i32 implements Default, Self becomes i32 and i32::default() returns 0. The trait definition doesn't know what Self will be, but the caller always does, because the call is always made on a concrete type.

The Implicit Sized Bound on Self

Every trait in Rust has an implicit Self: Sized bound. That means the compiler assumes Self has a known size at compile time unless you explicitly opt out. This has profound consequences for trait objects and for methods that take or return Self. Consider this innocent-looking trait:

trait Animal {
    fn speak(&self);
}

fn make_it_speak(a: &dyn Animal) {
    a.speak();
}

The function make_it_speak accepts a trait object &dyn Animal. This is allowed because speak takes &self and returns nothing; the compiler knows the size of a reference, and it doesn't need to know the concrete type behind it. Now add a method that returns Self:

trait Animal {
    fn speak(&self);
    fn reproduce(&self) -> Self; // Returns the concrete type
}

The trait object version breaks:

error[E0277]: the size for values of type `Self` cannot be known at compilation time

The problem is that Self inside the trait is Self: Sized by default, but when you use dyn Animal, the concrete type is erased. The compiler can no longer know what size the return value of reproduce should be, so it refuses to compile. To fix this when you still want dynamic dispatch, you can tell the method that Self must be sized by using a where clause:

trait Animal {
    fn speak(&self);
    fn reproduce(&self) -> Self
    where
        Self: Sized;
}

Now reproduce requires Self: Sized, so it is not available when calling through &dyn Animal. The trait object works for speak, but you cannot call reproduce on it. This is exactly how the standard library's Clone trait is defined: clone has an implicit Self: Sized bound, which is why you cannot clone a dyn Clone.

Object-safe methods cannot return Self by value:

If you intend a trait to be used as a trait object (dyn Trait), avoid returning Self from methods that lack a where Self: Sized bound. Otherwise the trait is not object-safe, and the compiler will reject any attempt to create dyn Trait.

If your trait genuinely needs to be object-safe and return the concrete type from some method, consider whether that method should be a generic function on the impl instead of a trait method. If the method really must be on the trait, the where Self: Sized escape hatch is the standard approach, but that method simply won't exist on trait objects.

Self and Object Safety — The Full Picture

Object safety is the set of rules that determine whether a trait can be made into a trait object. The rules that involve Self are:

  • Methods that take self by value are not object-safe, because the caller of a trait object doesn't know the concrete size.
  • Methods that return Self are not object-safe (unless bounded as above), for the same reason.
  • Generic methods (methods with type parameters) are not object-safe, because monomorphization can't happen with an erased type. All of these trace back to the fact that a trait object erases the concrete Self. Without Self: Sized somewhere, the compiler cannot determine stack allocation size or which monomorphized code to call.

Object safety error messages often mention Self:

When you encounter an error like the trait \X` cannot be made into an object, look for methods that take selfby value, returnSelf, or contain generic parameters. Adding where Self: Sizedto just those methods often resolves it, at the cost of making them unavailable ondyn X`.

Here is a real-world pattern you might see in library code:

trait Widget {
    fn draw(&self);
    fn clone_box(&self) -> Box<dyn Widget>
    where
        Self: Sized;
}

impl Clone for Box<dyn Widget> {
    fn clone(&self) -> Self {
        self.clone_box()
    }
}

The clone_box method requires Self: Sized, so it can't be called through a dyn Widget directly. But by implementing Clone on Box<dyn Widget>, you can clone trait objects indirectly. This trick is used in libraries like dyn-clone to work around the fact that Clone itself is not object-safe.

Self in Associated Items and Static Methods

Associated types and constants also live in the namespace of Self. When you define an associated type type Output;, you refer to it as Self::Output inside the trait definition and its default implementations. When the trait is implemented, Self::Output is replaced with the concrete type chosen in the impl block. Static methods (associated functions without a self parameter) also return Self frequently:

trait FromStr {
    type Err;
    fn from_str(s: &str) -> Result<Self, Self::Err>;
}

Here, Self appears both as the success variant of the Result and as part of Self::Err. The caller writes i32::from_str("42"), and the compiler knows that Self is i32. No ambiguity. When you need to distinguish between implementations of the same method from different traits, fully qualified syntax often uses Self:

trait A {
    fn f() -> i32;
}
trait B {
    fn f() -> String;
}
struct S;
impl A for S {
    fn f() -> i32 { 1 }
}
impl B for S {
    fn f() -> String { "hello".to_string() }
}

let x = <S as A>::f(); // 1
let y = <S as B>::f(); // "hello"

The notation <S as A>::f() makes the intended Self explicit. The compiler knows that inside impl A for S, Self is S.

Common Mistakes with Self in Traits

Several errors recur when developers first encounter Self in traits. Returning Self from default methods without thinking about trait objects. You might write a trait with a default method that constructs a new instance:

trait Spawnable {
    fn spawn() -> Self;
}

This works for concrete types like Player or Enemy, but the trait immediately becomes non-object-safe. If you later need dyn Spawnable, you will hit a compilation error. The solution is either to not make the trait object-safe, or to remove the -> Self method and provide an alternative factory via another trait or free function. Confusing Self with the type name when writing implementations. Inside impl MyTrait for MyType, you can use either MyType or Self interchangeably, but using Self keeps the code generic. If you later rename MyType, only the impl header needs to change — method bodies stay the same. This is a maintenance advantage, not a language requirement. Overlooking the implicit Sized bound. A trait with no explicit ?Sized bound will always assume Self: Sized. If you want to implement the trait for str or [T] (dynamically sized types), you must write:

trait MyTrait: ?Sized {
    fn method(&self);
}

The ?Sized syntax relaxes the bound: "Self may or may not be sized." Without it, implementing for str fails because str is not Sized.

Default methods and Self: Sized:

A method that contains self by value implicitly requires Self: Sized, because moving a value out of a reference isn't possible for unsized types. If you implement the trait for an unsized type, such a method is unavailable.

Summary

Self in traits is a type-level placeholder that makes it possible to write behavior once and apply it to many concrete types, without ever naming them. It works seamlessly with associated types, default implementations, and static methods. The lowercase self is just a convenient syntax for the receiver parameter. The critical distinction is that Self carries an implicit Sized bound, which locks in compile-time size knowledge. That bound is what makes generics efficient, but it also restricts dynamic dispatch. Recognizing when to keep that bound and when to relax it with where Self: Sized or ?Sized is the difference between a trait that works everywhere and one that unexpectedly locks you out of trait objects. If you need a trait to be object-safe, audit every method that mentions Self in a return position or takes self by value. The standard library's Clone and Iterator traits are excellent references: Clone uses Self pervasively and is not object-safe, while Iterator uses Self::Item and is object-safe for dynamic iteration through Box<dyn Iterator<Item = T>>.