Why may at most one of a placeholder's several requirements be a state-carrying type, where a language has single implementation inheritance?
answer
- only one slot is scarce
- state and layout, not behaviour
- no type descends from two
- behaviour contracts compose freely
- the state-carrying entry comes first
basics
~20 sBecause the combination must be satisfiable: some real type has to descend from every listed requirement at once. Under single implementation inheritance no type can descend from two state-carrying types, so a second one would make the requirement list unsatisfiable by construction.
solid answer
~40 sCombining requirements means demanding one type that satisfies all of them. Where a language allows a type only one implementation-carrying ancestor - one source of fields, layout and constructor chain - two such requirements in the same list could never both hold, so the list names an empty set of admissible types. Behaviour-only contracts declare operations without state or layout, so any number of them compose without conflict, which is why a combination is written as at most one class-like requirement plus as many behaviour contracts as the body needs. The restriction is also why notations fix the class-like entry in the first position: the compiled signature needs one representative for the placeholder, and where erasure applies that representative is the first entry in the list.
code
pseudocode · 9 lines// satisfiable: one state-carrying entry, then behaviour-only contracts
function publish<D requires BoardEntry, Orderable, Renderable>(items)
...
// unsatisfiable: two state-carrying entries
function publish<D requires BoardEntry, TimetableRow, Renderable>(items)
...
// no type can take layout and initialisation from both BoardEntry and TimetableRow,
// so the requirement list admits nothing at allgo deeper
Recall the shape of a combination: one class-like entry at most, then as many behaviour-only contracts as the routine needs, all demanded of the same placeholder.
Explain it from satisfiability: a list is a demand for one type under every entry, and where a type may inherit implementation only once, two such entries admit no type at all.
Show what the scarce slot changes in an API you own: if two requirements both want state, the decomposition is wrong, and the fix is a contract or a parameter rather than a cleverer bound.
Frame it as a portability question: the limit follows from a language's inheritance model, so a cross-language standard must state the dependency instead of asserting the rule.
## The rule, stated precisely When a placeholder is required to satisfy several types at once, the requirement list is a demand for one real type that sits under **every** entry simultaneously. In a language with **single implementation inheritance** - where a type may take fields, layout and a constructor chain from at most one ancestor - only one entry in the list may be that kind of type. The rest must be **behaviour-only contracts**: declarations of operations with no state and no layout of their own. The reason is satisfiability, not compiler convenience. If two entries were both implementation-carrying, a qualifying type would have to descend from both, which the language forbids outright. The list would describe a set of admissible type arguments that is empty, and a bound no type can ever satisfy is a signature nobody can call. ## What makes the implementation slot scarce A state-carrying type contributes three things that cannot simply be added together: - **Layout.** Its fields occupy space in every value of a descendant type. Two ancestors mean two layouts to merge, and any member reached through the wrong one reads the wrong storage. - **Initialisation.** Its constructor chain must run before the descendant's own. Two chains raise the question of order, and of what happens when both initialise a shared ancestor. - **Chosen implementations.** It supplies bodies, not just names. Two ancestors supplying the same operation leaves the language to pick a winner. A behaviour-only contract contributes none of these. It names operations the qualifying type must provide, and if two contracts name the same operation, one implementation in the qualifying type satisfies both. | | State-carrying requirement | Behaviour-only requirement | |---|---|---| | Contributes fields and layout | yes | no | | Contributes an initialisation chain | yes | no | | Supplies operation bodies | yes | only defaults, where the language has them | | How many may appear in one list | at most one | as many as the body needs | | Clash when two declare the same operation | must be resolved | satisfied by one implementation | ## Where languages differ Not every language draws the line in this place. Some allow a type to take implementation from several ancestors and answer the resulting questions explicitly - a fixed linearisation that decides which inherited operation wins, or a rule that forces the descendant to resolve the clash by hand. Others allow only behaviour-only contracts to be combined, and a few have no separate notion of a state-carrying type at all. What is common across all of them is the shape of the question: a combination is only meaningful if some type can satisfy the whole list, so whatever the language limits in inheritance, it limits in a requirement list too. ## The consequence at the compiled boundary A type system that discards type arguments after checking them still has to compile the routine's signature to something concrete. It picks one entry in the requirement list to stand for the placeholder, and the rule is positional: the **first** entry listed. Where members of the other entries are used, the compiler inserts conversions at the points where values cross the boundary. Two practical consequences follow: 1. A notation that permits a state-carrying entry requires it in the first position, so the representative is the entry that can actually carry the value's layout, and no reader has to wonder which one was chosen. 2. Ordering the remaining behaviour-only entries has no effect on meaning, and where a boundary crossing is hot, putting the most frequently reached contract first can remove conversions - a micro-concern, not a design rule. ## Designing with one slot - Treat the single implementation slot as the scarce resource: spend it on the requirement that genuinely owns state, and express every other capability as a behaviour-only contract. - If two candidate requirements both carry state, the decomposition is wrong before the bound is. One of them should be a contract, or the routine should take the capability as a parameter instead of demanding it from the type. - Remember the rule is about what may be **required together**, not about how many capabilities a concrete type may have. A qualifying type may implement a dozen behaviour contracts; the list only says which ones this routine insists on. - Do not assume the limit is universal. Where you must describe it in a design document, say what it depends on - single implementation inheritance - rather than stating it as a law of type systems.
- Where a language does allow several implementation-carrying ancestors, what new problem appears?Which inherited operation wins when two ancestors declare the same one, and how the state of each is laid out inside one value. Such languages answer with a fixed linearisation or a forced manual resolution; the single-slot rule avoids the question by never letting it arise.
- Why is the state-carrying entry written first in the requirement list rather than anywhere in it?Because a compiled signature needs one representative for the placeholder and the rule is positional - the first entry. Fixing the state-carrying one in that position makes the representative the entry that can carry the value's layout, and removes any argument about which was chosen.
saying these in an interview costs you the question
- Believes two state-carrying requirements combine if their members do not clash
- Says the limit exists because compilers check one requirement at a time
- Thinks behaviour-only contracts are also limited to one per placeholder
- Cannot name what a second implementation-carrying entry would make ambiguous
- States the one-slot rule as universal across all type systems