Skip to content

Trait or Mixin Composition Rule

Composition conflict rule — instantiates Controlled Inheritance Propagation

Builds a type by composing small reusable behavior units from a registry, with a conflict rule — linearization or explicit override — deciding what happens when two units define the same member.

A Trait or Mixin Composition Rule builds a type not by descending from a single parent but by composing several small, reusable behavior units — traits or mixins — drawn from a registry, and it supplies the rule for what happens when two of those units define the same thing. Its distinctive move among the siblings is that inheritance here is horizontal composition from a registry of reusable units, not a single vertical line — so its defining problem is the conflict rule: when mixin A and mixin B both provide save(), which one wins, or must the composing type resolve it explicitly? It is the sibling built around the diamond problem and its resolution.

Example

A web-service type is composed from three traits pulled from a shared registry: Serializable (adds to_json), Timestamped (adds created_at and touch), and Auditable (adds log_change and also a touch that writes an audit entry). Composing Timestamped with Auditable creates a collision — both define touch — and the composition rule decides the outcome.

Under a linearization rule, like Scala's trait linearization or Python's C3 method-resolution order, the traits are placed in a deterministic order and the later one wins, while the composing type may still call up the chain to run both. Under a strict rule, closer to Rust's, the ambiguity is a compile error until the type declares an explicit override of touch that names which behavior to keep or how to combine them. Either way the registry keeps the traits small and reusable, and the rule keeps their combination unambiguous — turning the classic diamond problem, where two paths to a shared member disagree, from a silent accident into a decided outcome.

How it works

  • Draw from a registry. Behavior comes from a curated set of small composable traits, each declaring what it provides and requires — not from one parent.
  • Compose into a type. Several units are mixed together, each contributing members.
  • Detect collisions. The rule finds members defined by more than one unit.
  • Resolve deterministically. Either linearize (a defined order picks the winner) or require the composing type to override the conflicted member explicitly — so the result is never ambiguous.

Tuning parameters

  • Conflict strategy — linearization (auto-pick by order) versus explicit-override-required (a compile error until resolved). Auto is convenient but can silently pick the wrong member; explicit is safe but verbose.
  • Order sensitivity — whether the order units are mixed changes the result. Order-dependent linearization is expressive but makes a reorder a behavior change; order-independent rules are predictable but less flexible.
  • Registry discipline — how strictly traits must declare their provided and required members, so conflicts and gaps are detectable before composition rather than after.
  • Override visibility — whether a composing type's resolution of a conflict must be explicit and named. Explicit overrides document intent; implicit ones bury it.

When it helps, and when it misleads

Its strength is escaping single-inheritance rigidity: behavior is factored into small reusable units and combined à la carte, with a rule that makes collisions deterministic instead of accidental. This is the disciplined answer to the diamond problem via linearization / method-resolution order[1], or via explicit resolution.

It misleads because composition hides provenance. A type mixing eight traits has no single place to read its behavior, and order-sensitive linearization means a harmless-looking reorder can flip which method runs. The classic misuse is stacking mixins to grab one convenient method while dragging in all of their required members and side effects — reuse that quietly imports far more than intended. The discipline that guards against it is to keep traits small and single-purpose, prefer an explicit override for any real conflict, and lean on the registry's declared provides/requires so a composition's true surface stays inspectable.

How it implements the components

  • mixin_or_trait_registry — the curated set of small, composable behavior units a type draws from; the mechanism's source of horizontal reuse.
  • precedence_and_conflict_rule — the linearization-or-explicit-override rule that resolves two units defining the same member; the mechanism's defining behavior.
  • override_scope_declaration — the composing type's explicit, member-scoped override that names which behavior wins when the rule demands resolution.

It does not model a single vertical lineage (lineage_model — that's Prototype Delegation Chain), cap composition depth (inheritance_depth_limit — also Prototype Delegation Chain), or track inherited risk (inherited_risk_registerPermission Inheritance with Explicit Denial).

Notes

Where class and prototype inheritance run vertically (one line of ancestors), composition runs horizontally (many peers merged at once) — which is exactly why this mechanism, and not its vertical siblings, has to carry a conflict rule. Single inheritance sidesteps the diamond problem by construction; composition earns its flexibility by resolving it.

References

[1] Method-resolution order is the deterministic sequence in which a composed type's traits or bases are searched for a member — Python's C3 linearization and Scala's trait linearization are concrete instances. It converts the diamond problem, where two inheritance paths offer the same member, into a defined winner rather than an ambiguity.