Can a reusable unit of behaviour own instance state of its own? Compare how several named languages answer, and say what goes wrong in each.
answer
- state reimports the diamond
- Scala: one copy per instance, linearization-ordered init, lazy val
- Ruby/Python: undeclared ivars, silent slot sharing
- Rust: required accessor, storage named by implementor
- Swift extensions: computed yes, stored no
basics
~20 sAnswers split three ways. Scala traits may declare fields, with a linearization-ordered initialization hazard. Ruby modules assign undeclared instance variables that silently collide. Rust, Java, C# and Swift forbid stored state and make the unit demand an accessor instead.
solid answer
~60 s- **Scala** traits may declare `val`/`var`; the compiler emits the field into each concrete class, so exactly one copy exists per instance. The hazard is timing: initializers run in linearization order, so a trait body reading a `val` supplied later sees null or zero - hence `lazy val` and, in Scala 2, early definitions. - **Ruby** modules declare nothing but freely assign `@ivars` on the includer. Two modules from two libraries that both use `@state` share one slot, with no error at definition, inclusion, or first write. The convention is to prefix variable names with the module name. - **Rust** traits hold no per-instance data at all; a trait requests storage as a required method (`fn buf(&mut self) -> &mut Vec<u8>`) and writes its provided bodies against it, so the implementor names the storage and collisions cannot happen. - **Java** interfaces and **C# 8** default interface members allow bodies but no fields; **Swift** protocol extensions allow computed but not stored properties. State is what re-imports the diamond into a trait system, so most designs forbid it.
code
ruby · 11 linesmodule Counter
def bump; @n = (@n || 0) + 1; end
end
module Retries
def retried; @n = (@n || 0) + 1; end
end
class Job; include Counter; include Retries; end
j = Job.new
j.bump; j.retried
# @n == 2 -- one slot, shared by two unrelated features, no warninggo deeper
Know that most reusable-behaviour units cannot hold their own fields, and that the ones which can usually pay for it somewhere.
Name a language on each side and give its concrete failure mode - an initialization-order surprise, or two modules writing the same undeclared variable.
Explain why state reimports the undecidable part of multiple inheritance, describe the required-accessor alternative, and evaluate a real design against these hazards.
Decide policy for a codebase or platform: whether units may own state at all, what naming or declaration discipline enforces it, and what that implies for units written by independent teams.
## Why state is the dividing line A reusable unit that only provides behaviour composes cleanly: worst case two units provide the same name and the composer picks. A unit that owns *storage* is different, because storage raises questions that composition alone cannot answer. If two units both want a field, is that one slot or two? If a unit is attached twice through different paths, does its state exist twice? When is the field initialized relative to the composing class's own constructor? Every language that permits stateful units has had to answer these, and every answer has a documented failure mode. ## Scala: declared fields, ordered initialization A Scala trait may declare `val`, `var` and even a concrete constructor body. The compiler resolves the copy question by fiat: the field is emitted into each concrete class that mixes the trait in, so one instance holds one copy no matter how the trait was reached. What it cannot resolve is ordering. Initializers run in linearization order, so if a trait's own body reads a `val` that a later participant in the linearization defines, it reads the field's default - null for references, zero for numbers - and does so without any type error. The result is a NullPointerException far from the cause, or worse, a silently wrong zero. Scala's answers are `lazy val` (defer evaluation to first read), `def` instead of `val` where recomputation is acceptable, and in Scala 2 the early-definitions syntax that evaluated selected vals before the superclass chain. The existence of three workarounds is itself the evidence that stateful units cost something. ## Ruby: undeclared, colliding, silent Ruby modules have no field declarations. A module simply writes `@count` on `self`, and `self` is whatever object included the module. Nothing about this is checked: two modules that both use `@count` end up sharing one slot on every object that includes both, and no diagnostic is produced at definition, at inclusion, or at the first conflicting write - the symptom is a counter that advances twice as fast, or a cache that is invalidated by an unrelated feature. The only defence is naming convention, which is why mature Ruby libraries prefix module instance variables with the module name. Python's mixins share the hazard for the same reason: one attribute dictionary per instance and no declaration of ownership. ## Rust: state as a required capability Rust traits carry no per-instance data at all - only associated types, associated consts and methods. A trait that needs storage declares a required accessor, for example `fn buffer(&mut self) -> &mut Vec<u8>`, and writes its provided methods against that accessor. The implementor decides where the bytes live and whether two traits share one field or get separate ones, and that decision is written down in ordinary code a reviewer can read. Because nothing is injected implicitly, two traits can never collide over a slot neither declared. Rust also gets a property no mixin system has: coherence rules prevent two crates from providing the same trait implementation for the same type, so the cross-library collision case does not exist. ## Java, C#, Swift: bodies without fields Java 8 default methods and C# 8 default interface members were introduced to let published interfaces gain methods without breaking implementors - API evolution, not composition. Neither allows instance fields, so the state diamond simply cannot arise; a library that needs per-implementor state declares an abstract accessor, or ships a small helper object the implementor stores and delegates to. Swift lands in the same place from a different direction: a protocol extension can add a *computed* property (a getter, possibly a setter) but never a stored one; conformers declare the storage themselves, and code that needs to attach storage to types it does not own reaches for the Objective-C runtime's associated-object side table, which is a workaround with real costs around lifetime and thread safety. ## What to say in the interview State the three-way split with named languages, then make the theoretical point: allowing a unit to own storage reimports exactly the part of multiple inheritance that has no default answer. Flattening in particular has no story for state - if composition means "as if written directly in the class", then two units contributing the same field name are contributing the same field, which is almost never what either author meant. That is why the original trait model was defined as stateless, and why the later "stateful traits" research had to add explicit operations for merging and freezing state rather than letting it fall out of composition. A good closing observation is that the accessor-requirement pattern is available in every language, including the ones that permit stateful units: you can write Scala traits with `def buffer: Buffer` instead of `val buffer`, and get the same auditability at the cost of one line per implementor.
- Scala emits a trait's field into each concrete class. Why does that not solve the problem?It answers the copy question - one field per instance - but not the timing question. Initializers run in linearization order, so a trait body that reads a value contributed by a later participant sees null or zero with no type error. The workarounds are `lazy val`, using `def` where recomputation is fine, and in Scala 2 early definitions; the fact that three exist is the signal that stateful units carry a real cost.
- How would you write a reusable unit that needs state in a language that forbids stored properties in the unit?Declare an abstract accessor the implementor must supply and write all provided bodies against it, which is the Rust trait pattern and works equally with Java interfaces or Swift protocols. If more than one field is needed, hand out a small state object that the implementor stores and returns from a single accessor. Both make the storage decision visible and prevent two units from sharing a slot neither declared.
- Why was the original trait model defined as stateless?Because composition was defined as flattening - the composed class behaves as if the methods were written directly into it - and that definition gives no answer for fields: two units contributing the same field name would be contributing the same field, which is almost never intended, and there is no ordering to break the tie. Keeping traits stateless preserved order-independence. Later stateful-trait research had to add explicit merge and freeze operations rather than letting state fall out of composition.
saying these in an interview costs you the question
- Saying a stateful trait's field is shared across all instances that mix it in - it is per instance; the real hazards are name collision and initialization order.
- Claiming Java interfaces can hold state because they can hold constants - those are static finals, not per-instance storage.
- Assuming Swift protocol extensions can add stored properties; only computed ones are allowed.
- Treating undeclared instance variables in modules as harmless because collisions are rare, when the failure is silent and shows up as corrupted counters or caches.
- Presenting the required-accessor pattern as unavailable outside Rust - it is expressible in any language with abstract members.