In several languages a contract can constrain a type parameter at compile time yet cannot be used as the type of a runtime value that holds some unknown implementer: Rust rejects certain traits behind `dyn`, Swift distinguishes `any P` from `some P`, Go's method-set rule decides whether a value or only a pointer satisfies an interface, and C# forbids calling a `static abstract` interface member on an interface-typed variable. Which kinds of member make a contract unusable as a runtime value type, and how do you design a contract so it stays usable?
answer
- constraint use vs value use — only the second has rules
- one address, nameable signature, a receiver to dispatch from
- Rust: generic method / Self return / static ⇒ not dyn; `where Self: Sized` opts out
- Swift: `some` = one hidden concrete type, `any` = box; associated type in parameter position blocked
- Go: pointer-receiver method ⇒ only *T satisfies; nil *T in interface ≠ nil
basics
~20 sA contract can type a runtime value only if every member is callable on a receiver whose concrete type is unknown. Generic methods, Self-returning methods and receiver-less statics are not. Rust bars those traits from dyn, Swift splits any from some, Go keeps pointer-receiver methods out of a value's method set.
solid answer
~60 sA contract gets used two ways: as a **compile-time constraint** on a type parameter, where the concrete type is known, and as the **type of a value** holding an unknown implementer, where it is erased. Only the second imposes rules. - **Rust**: a trait is `dyn`-compatible only if no method is generic over its own type parameter, returns `Self`, or lacks a receiver, and the trait has no associated constants or generic associated types. `where Self: Sized` opts one method out so the rest still works as `dyn Trait`. - **Swift**: `some P` is one fixed concrete type; `any P` is a box. Since 5.7 every protocol has an `any`, but members mentioning `Self` or an associated type in *parameter* position stay uncallable on it. - **Go**: pointer-receiver methods belong to `*T`'s method set, not `T`'s — so `T` does not satisfy the interface, and an interface holding a nil `*T` is itself non-nil. - **C#**: `static abstract` members are reachable only through a type parameter. Design fix: split a small dispatchable core from the generic extension layer.
code
text · 11 linescontract Repo:
save(self, item) // OK: one body, found via the receiver
find<Q: Query>(self, q: Q) // generic: a family of bodies, no single entry
copy(self) -> Self // returns a type the erased caller cannot name
make() -> Self // no receiver: nothing to dispatch from
const NAME // per-type constant, not per-instance
value of type "some unknown Repo" // legal only if EVERY member above is dispatchable
// repair: mark find/make/copy "only when the concrete type is known",
// or split -> RepoCore (dispatchable) + RepoExt (generic, blanket-implemented)go deeper
Know that in Java and Kotlin every interface can type a variable, and that this is not universal — Go, Rust and Swift each have rules about which contracts may hold an unknown implementer.
Name the disqualifying member shapes — a method generic over its own type parameter, a method returning Self, a receiver-less static — and state Go's method-set rule with a concrete example of value versus pointer.
Read the compiler error and fix it: opt the offending method out with where Self: Sized, split a dyn-compatible core from a generic extension, or return &t instead of t. Recognise the typed-nil interface bug on sight.
Treat value-type eligibility as an API design constraint chosen when the contract is written, not discovered at the first call site. Weigh erased dispatch against monomorphisation, identify which boundaries genuinely need heterogeneous collections or plugin registries, and make the split a review rule for public contracts.
## Two different jobs for one contract An interface-like contract is used in two structurally different ways. 1. **As a constraint**: `for any T that satisfies C, ...`. The compiler knows the concrete `T` at each use site. It can compile a separate copy of the code per `T` (monomorphisation) or at least resolve every member statically. 2. **As the type of a value**: a variable, a struct field, an element of a heterogeneous collection, a parameter that accepts *any* implementer. Here the concrete type is erased at compile time; all that survives at runtime is a value plus some runtime description of how to call its members. In Java, C# and Kotlin these two jobs look identical, so the question never arises. In Rust, Swift and Go they come apart, and some contracts can do job 1 but not job 2. The name for a value of the second kind is an *existential*: "there exists some type satisfying C, and this value is one of them." ## The survival rule For job 2, every member of the contract must be callable when the only thing you have is the value and a runtime dispatch record. That imposes three requirements on each member: - **One code address.** A member generic over its own type parameter is not one function; it is a family compiled once per instantiation. There is no single entry to record, and the instantiations that will be needed are not knowable from the erased value. - **A statically describable signature.** A member returning `Self` returns something whose type and size the caller cannot name once `Self` is erased. Same for a parameter typed `Self`: the caller would have to prove two erased values share a concrete type, which erasure just destroyed. - **A receiver to dispatch from.** A member with no receiver (a static or a constructor requirement) has nothing to look the implementation up on. Knowing the concrete type is the whole point of such a member. Everything below is a language spelling this rule. ## Rust: dyn compatibility, and the opt-out Rust calls a trait *dyn-compatible* (older docs: *object-safe*) when `dyn Trait` is legal. Disqualifiers: a method with its own generic type parameters; `Self` in a return or non-receiver parameter position; `-> impl Trait` in a trait method and `async fn` in a trait (both are sugar for an opaque associated type); a receiver-less associated function; associated constants; generic associated types; a `Self: Sized` bound on the trait itself. Associated types are fine as long as the `dyn` type pins them (`dyn Iterator<Item = u32>`). The escape hatch is per-method: annotate the offending method `where Self: Sized` and it leaves the dispatch record. It stays callable on concrete types and through generics, and the trait becomes usable as `dyn Trait` for everything else. The larger idiom is to split a small dyn-compatible core trait from an extension trait carrying the generic conveniences, blanket-implemented for every implementer of the core. ## Swift: `any` versus `some` Swift makes the distinction syntactic. `some P` is an *opaque* type: exactly one concrete type, chosen by the implementation and hidden from the caller, resolved statically — associated types are fully usable. `any P` is an existential box. Before Swift 5.7 a protocol with associated types or `Self` requirements could not be used as a value type at all ("can only be used as a generic constraint"). SE-0309 lifted the blanket ban: every protocol now has an `any` form. What did not change is which *members* you can reach on it. A member whose signature uses `Self` or an associated type in a return position can be erased upward and called; one that uses it in a parameter position cannot, because you would have to supply a value of a type nobody can name. Primary associated types (SE-0346) let you pin them — `any Collection<Int>` restores the members that mention `Element`. Separately, `any P` generally does not itself satisfy a `T: P` constraint; implicit opening of existentials covers many call sites but is not a promise that the box conforms. ## Go: method sets, not member shapes Go's restriction has a different cause. A method declared with a pointer receiver needs an *addressable* receiver so it can mutate. An interface value holds a copy of what you assigned it, and that copy is not addressable, so the language keeps such methods out of the value type's method set: methods on `T` belong to both `T` and `*T`; methods on `*T` belong only to `*T`. Assigning a `T` where the interface needs a pointer-receiver method is a compile error; assigning `&t` works. The convenience where `t.Mutate()` compiles for a plain variable is the compiler inserting `&t`, and it applies only to addressable expressions — never to interface satisfaction. The companion trap: an interface value is a (dynamic type, pointer) pair, so assigning a nil `*T` produces an interface that is **not** equal to nil. The classic bug is a function declaring `err error`, returning a nil `*MyError`, and every caller's `if err != nil` firing. ## C#: constraint-only members C# 11's `static abstract` interface members exist for generic math. They can be invoked only through a type parameter constrained by the interface, never on an interface-typed variable — there is no instance from which to pick an implementation. The interface is also rejected as a type argument satisfying its own constraint: it does not conform to itself. ## Designing for it Decide at design time whether the contract must ever type a value. If yes, keep it narrow and dispatchable: object-shaped members with a receiver, no `Self` in signatures, no per-method generics, factory functions moved to a separate builder contract. If the generic power is essential, split: a dyn-eligible core plus an extension layer, which is exactly how Rust's ecosystem structures `Iterator`-shaped traits.
- A Rust trait needs one generic helper method but you also want to store implementers as `dyn Trait`. What do you do?Annotate that method `where Self: Sized`. It is then excluded from the vtable, so it cannot be called through `dyn Trait`, but the trait as a whole becomes dyn-compatible again and the method stays callable on concrete types and in generic code. The bigger-hammer version is to split the contract: a small dyn-compatible core trait, plus an extension trait carrying the generic conveniences and blanket-implemented for every `T: Core`.
- Swift 5.7 allows `any P` for protocols with associated types. What still does not work?Members that mention `Self` or an associated type in a non-covariant position — typically a parameter — remain uncallable on the existential, because the caller would have to produce a value of a type erasure just discarded; return positions are erased upward and stay usable. Primary associated types let you pin the type (`any Collection<Int>`) and recover those members. And `any P` still does not generally satisfy a `T: P` constraint, though implicit opening of existentials covers many call sites.
- Why does a pointer-receiver method in Go exclude the plain value type from the interface, when `c.Inc()` compiles fine on a local variable?A pointer-receiver method needs an addressable receiver so mutation is visible. A local variable is addressable, so the compiler silently rewrites `c.Inc()` as `(&c).Inc()`. An interface value stores a copy, and that copy is not addressable, so the language keeps pointer-receiver methods out of `T`'s method set entirely rather than mutating a copy nobody can observe. Assign `&c` instead.
- Why do Java and Kotlin never hit this problem?They give every reference type a uniform representation — a pointer plus a runtime class — and erase generics, so any method can be reached through an interface-typed reference regardless of its signature. A method generic over `<T>` compiles to a single erased body, and a `Self`-shaped API is simulated with an F-bound like `T extends Comparable<T>`, which weakens static guarantees but never makes the interface unusable as a variable type.
A constraint is hiring someone whose résumé you have read; an existential is a sealed envelope with a label. Anything the label cannot describe — 'returns another of me', 'call this before anyone exists' — cannot be requested through the envelope, no matter who is inside.
saying these in an interview costs you the question
- Assuming any interface can always be used as a variable type — a Java/Kotlin habit that is false in Rust, Swift and (for the wrong receiver) Go.
- Saying Rust forbids generic methods in traits. It only forbids them behind `dyn`; the trait is fine, and `where Self: Sized` opts the method out.
- Claiming a Go type with a pointer-receiver method "does not implement the interface" — the pointer type does; only the value type's method set falls short.
- Believing an interface holding a nil pointer compares equal to nil, so `if err != nil` is safe after returning a typed nil.
- Treating Swift's `some P` as sugar for `any P`. `some` is one fixed concrete type resolved statically with full access to associated types; `any` is a box whose contents can vary.