| name | rust-syntax-traits |
| description | Use when the user defines or implements a trait, asks about default methods / supertraits / marker traits / blanket impls / sealed traits, encounters orphan-rule errors, or needs to choose between inherent impl and trait impl. Prevents orphan-rule violations, accidentally exposing an extensible trait users can implement, and confusing inherent vs trait methods. Covers: trait definition syntax (required + default methods), supertraits, marker traits (Send / Sync / Sized / Copy / Unpin), blanket impls, sealed trait pattern, inherent vs trait impl, orphan rule + coherence, derive macros (Copy / Clone / Debug / PartialEq / Eq / Hash / Default). Keywords: trait, "trait definition", "default method", supertrait, "marker trait", "blanket impl", "sealed trait", "orphan rule", coherence, "inherent impl", "impl Trait for Type", "newtype wrapper", "cannot impl foreign trait", derive, "#[derive]", "method not found", E0117, E0119, E0120, E0210, "what does trait do", "how to add method".
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Rust 1.85+, edition 2024. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
rust-syntax-traits
The mechanics of trait declaration and implementation in Rust: how to define a trait, how default methods work, how supertraits compose, how the orphan rule and coherence constrain impl Trait for Type, how to seal a trait against external implementors, and which built-in marker traits (Send, Sync, Sized, Copy, Unpin) have special compiler treatment.
Cross-references: [[rust-syntax-generics]] (trait bounds on type parameters) [[rust-syntax-trait-objects]] (dyn Trait and dyn-compatibility) [[rust-syntax-gats]] (associated types with generics) [[rust-errors-trait-bounds]] (E-codes for unsatisfied bounds) [[rust-core-memory-model]] (Send / Sync auto-trait rules)
When to use this skill
- User writes
trait Foo { ... } or impl Trait for Type { ... } and needs the rules
- User hits an orphan-rule error (E0117) or coherence error (E0119) and asks how to fix it
- User needs to add a method to a trait without breaking downstream implementors
- User asks about
Send / Sync / Sized / Copy / Unpin and how to opt in or out
- User wants to write an extensible-looking trait that only the defining crate may implement (sealed trait)
- User confuses inherent methods (
impl Foo) with trait methods (impl Trait for Foo)
- User asks about
#[derive(...)] and which derives are available in std
For dyn Trait and object safety see [[rust-syntax-trait-objects]]. For generic-parameter trait bounds (fn foo<T: Trait>()) see [[rust-syntax-generics]].
Trait definition syntax (Reference, verbatim)
unsafe? trait IDENTIFIER GenericParams? ( : TypeParamBounds? )? WhereClause? {
InnerAttribute*
AssociatedItem*
}
Source: Rust Reference: items/traits
A trait body consists of associated items:
trait Example {
const CONST_NO_DEFAULT: i32;
const CONST_WITH_DEFAULT: i32 = 99;
type TypeNoDefault;
fn method_without_default(&self);
fn method_with_default(&self) {}
}
Each associated item is either required (no body) or provided (has a body / default value). Implementors must provide every required item; they MAY override any provided one.
ALWAYS prefer a provided (default) method when the behaviour can be expressed in terms of other trait methods. This shields downstream implementors from breakage when you later refine the default.
NEVER add a new required method to a published trait: it is a breaking change. Add a method WITH a default body instead.
Quick reference table
| Concept | Syntax | Compiler rule |
|---|
| Required method | fn m(&self) -> T; | Implementors MUST provide a body |
| Default method | fn m(&self) -> T { ... } | Implementors MAY override |
| Associated type | type Item; | One concrete Item per impl |
| Associated const | const N: u32; (or = 0) | One value per impl |
| Supertrait | trait Sub: Super { ... } | Implementor of Sub MUST also impl Super |
| Marker trait | trait Marker {} (often unsafe trait) | No items, used purely as a tag |
| Blanket impl | impl<T: A> B for T { ... } | Applies to every T that satisfies the bound |
| Sealed trait | private supertrait | Outsiders cannot implement |
| Inherent impl | impl Foo { ... } | Methods belong to Foo directly |
| Trait impl | impl Trait for Foo { ... } | Methods belong to Trait for Foo |
| Orphan rule (E0117) | local trait OR local type | Foreign-trait-for-foreign-type rejected |
| Coherence (E0119) | one impl per (Trait, Type) | Overlapping impls rejected |
Required vs default (provided) methods
pub trait Greeter {
fn name(&self) -> &str;
fn greet(&self) -> String {
format!("Hello, {}!", self.name())
}
}
struct World;
impl Greeter for World {
fn name(&self) -> &str { "world" }
}
Source: Rust Reference: items/traits Source: Rust Book Ch 10.2: Traits
ALWAYS write defaults in terms of required methods so the contract stays composable. NEVER let a default method call itself directly; that compiles but recurses at runtime.
Supertraits
A supertrait is a trait bound on the trait itself. Two equivalent forms:
trait Circle: Shape { fn radius(&self) -> f64; }
trait Circle where Self: Shape { fn radius(&self) -> f64; }
"Supertraits are traits that are required to be implemented for a type to implement a specific trait."
"Anywhere a generic or trait object is bounded by a trait, it has access to the associated items of its supertraits."
Source: Rust Reference: items/traits
Implications:
- Any type that implements
Circle MUST also implement Shape.
- Inside
Circle's default methods and inside dyn Circle you can call Shape's methods directly.
- Removing a supertrait is a breaking change; adding one is too unless every existing implementor already happens to implement it.
ALWAYS list a supertrait when the subtrait genuinely needs the supertrait's items. NEVER use a supertrait as a "convenience bound" you don't actually rely on; it locks downstream code into implementing it too.
Marker traits (Send / Sync / Sized / Copy / Unpin)
A marker trait has no items. The compiler gives several of them special meaning. From the Reference's special-types-and-traits page:
| Trait | Meaning | How it is implemented |
|---|
Sized | "The size of this type is known at compile-time." Implicit bound on every type parameter unless relaxed with ?Sized. | Automatic; cannot be implemented manually. |
Copy | Bit-for-bit duplicable on assignment. Requires Clone as supertrait. | #[derive(Copy, Clone)] (or manual). All fields MUST be Copy. A Drop impl excludes Copy (E0184). |
Send | Safe to move to another thread. | Auto trait: derived structurally from field types. unsafe impl Send for T {} to assert manually. |
Sync | Safe to share &T across threads. | Auto trait, same rules as Send. |
Unpin | Safe to move out of a Pin<&mut T>. | Auto trait. Opt out by storing PhantomPinned in a field. |
Source: Rust Reference: special-types-and-traits
Auto traits (Send / Sync / Unpin)
An auto trait is implemented for a composite type if and only if every field implements it. The compiler does this without any impl block.
ALWAYS think of Send, Sync, and Unpin as structural: change a field type and the whole struct's Send-ness can flip.
NEVER write unsafe impl Send for T {} without a soundness comment explaining why every field can in fact be moved across threads. Use a PhantomData<*mut ()> field to OPT OUT of Send/Sync (the raw-pointer trick).
Sized and ?Sized
fn takes_sized<T>(_: T) {}
fn takes_unsized<T: ?Sized>(_: &T) {}
The Reference is explicit: "Type parameters (except Self in traits) are Sized by default, as are associated types. These implicit Sized bounds may be relaxed by using the special ?Sized bound." Source: Rust Reference: special-types-and-traits
?Sized MAY appear only on type parameters and Self. It MUST never be combined with a value receiver (a ?Sized value cannot live on the stack).
Blanket impls
A blanket impl uses a generic parameter for the implementing type:
pub trait Stringify {
fn stringify(&self) -> String;
}
impl<T: std::fmt::Display> Stringify for T {
fn stringify(&self) -> String { format!("{self}") }
}
Constraints:
- A blanket impl rules out any further
impl Stringify for SomeType for types matching the bound. Coherence (E0119) makes this an error.
- Adding a NEW blanket impl to a published trait is a breaking change: it can conflict with downstream
impls.
- The classic stdlib example is
impl<T: Clone> ToOwned for T.
ALWAYS reserve blanket impls for "default behaviour for all T: Bound" semantics. NEVER ship a blanket impl alongside a contradictory per-type impl: coherence will reject one and your API will need a redesign.
Sealed trait pattern
The orphan rule (below) lets crates outside yours implement a pub trait you publish for their own types. If you do NOT want that, seal the trait: declare it as a subtrait of a PRIVATE supertrait. Only your crate can implement the private supertrait, so only your crate can implement the public trait.
pub trait Operation: private::Sealed {
fn run(&self) -> u32;
}
mod private {
pub trait Sealed {}
}
pub struct Add;
impl private::Sealed for Add {}
impl Operation for Add { fn run(&self) -> u32 { 1 + 2 } }
Properties:
- External crates can
use Operation and CALL op.run().
- External crates CANNOT write
impl Operation for MyType (E0445 / E0446 on the private supertrait).
- You retain freedom to add required methods to
Operation without breaking downstream callers, because they cannot have implementors.
ALWAYS seal a trait whose method set you might extend in a non-major release. NEVER seal a trait whose entire purpose is downstream extension (e.g. serde::Serialize); that defeats the contract.
Source: Rust API Guidelines: future-proofing
Inherent impl vs trait impl
struct Counter { n: u32 }
impl Counter {
fn new() -> Self { Self { n: 0 } }
fn tick(&mut self) { self.n += 1; }
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
self.tick();
Some(self.n)
}
}
Rules:
- A type may have any number of inherent
impl blocks AND any number of trait impl blocks.
- Inherent methods take precedence in method resolution; trait methods are reached only when the trait is in scope.
- An inherent method MAY shadow a trait method of the same name. The trait method is then callable only via fully qualified path:
Trait::method(&counter).
ALWAYS write Iterator::next(&mut c) (or import Iterator and call c.next()) when a trait method is shadowed. NEVER assume a trait method "just works" without importing the trait or relying on the prelude.
Source: Rust Reference: items/implementations
Orphan rule and coherence
"A trait implementation is only allowed if either the trait or at least one of the types in the implementation is defined in the current crate."
Source: Rust Reference: items/implementations
This is the orphan rule (E0117). It exists so that adding a dependency cannot retroactively change which impl your code resolves to.
| Trait location | First type parameter location | Allowed? |
|---|
| Local crate | Local or foreign | YES |
| Foreign crate | Local | YES |
| Foreign crate | Foreign | NO (E0117) |
The fix for the forbidden case is the newtype wrapper:
pub struct Bytes(pub Vec<u8>);
impl std::fmt::Display for Bytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for b in &self.0 { write!(f, "{b:02x}")?; }
Ok(())
}
}
Coherence (E0119) is the sibling rule: at most one impl of a given trait may apply to a given concrete type. Overlapping blanket impls and a blanket-plus-specific combination both fail E0119.
ALWAYS wrap a foreign type in a local newtype when you need to implement a foreign trait for it. NEVER try to add an impl ForeignTrait for ForeignType even via a re-export trick: the orphan rule is checked on the concrete crate identity, not the import path.
Source: Rust Reference: items/implementations
Decision tree: trait impl vs inherent impl
Need to attach behaviour to a type?
|
+-- Will the behaviour be used through a generic bound or dyn Trait?
| |
| +-- YES -> trait impl (impl Trait for Type)
| | Required when the method must be invoked polymorphically.
| |
| +-- NO -> step deeper
|
+-- Is there an existing trait in std or a dep whose contract fits exactly
| (Display, From, Iterator, IntoIterator, Default, AsRef<T>, etc.)?
| |
| +-- YES -> trait impl for that existing trait.
| ALWAYS prefer matching a standard trait over a custom one.
| |
| +-- NO -> step deeper
|
+-- Is the behaviour purely type-specific, with no polymorphism in sight?
|
+-- YES -> inherent impl (impl Type { ... })
+-- NO -> design a new trait + trait impl.
ALWAYS implement standard traits when their contract matches the operation (Display, From, Default, ...). NEVER invent a custom trait that duplicates an existing std contract: callers will be unable to plug your type into generic code.
Derive macros for built-in traits
#[derive(...)] is sugar that asks the compiler to synthesise an implementation. The std-supported derives:
| Trait | Behaviour produced by the derive | Requires |
|---|
Copy | Marker only (no method). | Clone MUST be in the same derive. All fields Copy. No Drop. |
Clone | Field-by-field clone. | All fields Clone. |
Debug | Struct/enum dump in {:?} formatter. | All fields Debug. |
PartialEq | Field-by-field ==. | All fields PartialEq. |
Eq | Marker only (asserts reflexive equality). | All fields Eq. |
Hash | Field-by-field hash. | All fields Hash. |
Default | Calls Default::default() per field. | All fields Default. |
PartialOrd | Lexicographic on field order. | All fields PartialOrd. |
Ord | Lexicographic on field order. | All fields Ord and Eq. |
Source: Rust Reference: derive
ALWAYS group derives in declaration order: #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]. NEVER derive Copy without Clone (E0204): every Copy type is Clone.
ALWAYS think about whether the derive's structural definition is the contract you want. The classic mistake: #[derive(PartialEq)] on a type with a cached hash field will compare the cache too, producing false negatives.
Common error codes
| Code | Trigger | Fix |
|---|
| E0117 | Orphan rule: foreign trait + foreign type | Wrap one of them in a local newtype |
| E0119 | Coherence: two overlapping impls | Make the impls non-overlapping or remove one |
| E0120 | Drop impl for type that is not a struct/enum/union | Implement Drop only on a concrete owned type |
| E0184 | #[derive(Copy)] on a type with Drop impl | Drop OR Copy, never both |
| E0204 | #[derive(Copy)] and a field is not Copy | Remove Copy, keep Clone |
| E0210 | Type parameter must be used as the first local type | Reorder generics so a local type appears first |
| E0277 | Trait bound not satisfied | Add an impl, add a bound, or change the type |
| E0599 | Method not found (trait not in scope) | use Trait; to bring the trait into scope |
Full E-code drilldown lives in [[rust-errors-trait-bounds]].
Section: Avoid these mistakes
(Full list with WHYs in references/anti-patterns.md.)
- Trying to
impl ForeignTrait for ForeignType (orphan rule, E0117). Fix: newtype wrapper.
- Deriving
Copy without Clone in the same attribute (E0204). Both are required together.
- Implementing
Drop AND deriving Copy on the same type (E0184). They are mutually exclusive by design.
- Publishing an extensible-looking
pub trait without sealing it, then trying to add a required method later (breaking change). Seal first, extend safely later.
- Using an inherent
impl when the method must be invoked through dyn or a generic T: Trait bound. Inherent methods are not part of any trait and cannot be dispatched polymorphically.
- Adding a new required method to a published trait. ALWAYS add it with a default body or behind a sealed trait.
- Writing
unsafe impl Send for T {} without a soundness comment. Reviewers cannot verify the claim and the compiler will not catch a mistake.
Reference links
For deeper drill-downs see:
references/methods.md: trait, supertrait, derive, and marker-trait signatures verbatim from the docs.
references/examples.md: complete working examples for default methods, supertraits, blanket impls, sealed traits, newtype wrappers, and derives.
references/anti-patterns.md: the most common trait-design and impl mistakes with root-cause explanations and fixes.