A type system specifies how program components can be legally combined. Type checking validates rules, while type inference fills in omitted type information. Which errors they exclude depends on the specific rules and their soundness; they cannot guarantee the absence of all runtime errors or business logic errors.
Formal proofs of type safety typically discuss how well-typed programs avoid getting stuck in certain rule-less executable states; whether null pointer dereferences, array out-of-bounds, or division by zero are statically excluded depends on how the language models these operations.
Type Rules: Connecting Local Judgments
Γ ⊢ e : T denotes that expression e has type T in environment Γ. Simple integer addition rules require both sides to be Int, resulting in Int; if requires the condition to be Bool, and both branches must satisfy the language-defined common result type.
Function calls first check callability and argument count, then check parameter types and other constraints item by item. Simply zipping pairs would miss extra or missing arguments. Real languages may also involve default arguments, variadic arguments, overloading, and coercions.
Checking is not necessarily bottom-up. Bidirectional type checking combines "synthesizing types from expressions" and "checking expressions under expected types," leveraging context to reduce ambiguity and improve error locations.
Type Representation and Identity
Internal representations typically distinguish basic types, functions, type constructor applications, type variables, and declaration identities. Recursive types can be linked via IDs or indirect references and should not infinitely copy the entire structure.
Nominal type identity comes from declarations, not simple string name comparison: two modules both declaring Point may still be different types. Structural rules, on the other hand, determine specific relationships based on constituent parts. Type equality, assignment compatibility, subtyping, and explicit casting are distinct problems.
Languages can also mix rules. Go's named type identity differs from its unnamed structural type rules, and interface implementation relies on method sets; it cannot be generalized as "all structurally identical structs in Go are the same type." Go Type Identity Rules
Unification: Finding Substitutions That Make Type Expressions Equal
Introduce variables α, β for unknown types. For the function application f(3), it can produce type(f)=Int→α. If the context requires the result to be Bool, add α=Bool.
Unification recursively compares constructors: List<α> and List<Int> yield α=Int; function types compare parameters and results item by item; Int and Bool fail. Substitutions must propagate consistently, not just modify the current type name in one place.
The occurs check prevents establishing disallowed infinite types, such as α=α→β. If α appears in the type to be bound, it should be rejected under standard HM type syntax. Ignoring this step may cause the solver to loop or accept types beyond the model.
A Minimal Unifier: Apply Old Substitutions First, Then Establish New Constraints
Strings starting with ? represent type variables, ordinary strings represent base types, and tuples represent constructor applications, e.g., ("List", "?a"). Function types are uniformly represented as binary constructors Fn; this is a finite first-order type term model that does not handle subtyping, overloading, recursive types, or full HM generalization.
defvariable(t):returnisinstance(t,str)andt.startswith("?")defresolve(t,substitution):ifvariable(t)andtinsubstitution:returnresolve(substitution[t],substitution)ifisinstance(t,tuple):return(t[0],*(resolve(x,substitution)forxint[1:]))returntdefoccurs(v,t):returnv==tor(isinstance(t,tuple)andany(occurs(v,x)forxint[1:]))defunify(a,b,substitution):a, b=resolve(a,substitution), resolve(b,substitution)ifa==b:returnifvariable(a):ifoccurs(a,b):raiseTypeError("infinite type")substitution[a]=breturnifvariable(b):unify(b,a,substitution)returnif(isinstance(a,tuple)andisinstance(b,tuple)anda[0]==b[0]andlen(a)==len(b)):forx,yinzip(a[1:],b[1:]):unify(x,y,substitution)returnraiseTypeError("type constructor mismatch")subst={}unify(("Fn","?a","?b"),("Fn","Int",("List","?c")),subst)unify("?c","Bool",subst)assertresolve(("Fn","?a","?b"),subst)==("Fn","Int",("List","Bool"))fora,bin[("Int","Bool"),("?a",("Fn","?a","?b")),(("Pair","Int"),("Pair","Int","Bool"))]:try:unify(a,b,{})exceptTypeError:passelse:raiseAssertionError("incompatible types accepted")# Indirect self-references must also be rejected: after ?a = ?b, ?b = List<?a>.
indirect={}unify("?a","?b",indirect)try:unify("?b",("List","?a"),indirect)exceptTypeError:passelse:raiseAssertionError("indirect occurs check missed")
The first constraint yields ?a=Int, ?b=List<?c>, followed by ?c=Bool. When reading the function type, substitutions must be applied recursively to obtain Int→List; modifying only the current node would leave ?c unupdated. The indirect self-reference test shows that the occurs check must be performed after existing substitutions are interpreted.
Failures here terminate the current solving process immediately. If used for overload candidate probing, previous recursive steps may have already modified substitution; upon failure, that branch should be discarded or rolled back, rather than continuing with a half-successful substitution to try the next candidate. Input construction of type terms should be guaranteed valid by the frontend; real implementations need additional depth and workload limits.
Where must a substitution propagate to solve a type?
Every occurrence of a variable must mean the same substitution. Select compatible constraints or build constructor conflicts and indirect cycles. Failure invalidates the entire solve; its trace explains where it failed, not a partial substitution to reuse in another candidate.
Preparing the visual
Where must a substitution propagate to solve a type?
Unification is more than renaming. A later beta binding must propagate through alpha→List(beta); indirect cycles require occurs checking after substitution.
{"id":"unification-substitution","title":"Where must a substitution propagate to solve a type?","summary":"Unification is more than renaming. A later beta binding must propagate through alpha→List(beta); indirect cycles require occurs checking after substitution.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"c1\" checked><span data-i18n=\"c1\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"c2\" checked><span data-i18n=\"c2\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"c3\"><span data-i18n=\"c3\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"c4\"><span data-i18n=\"c4\"></span></label><section class=\"panel\"><h3 data-i18n=\"original\"></h3><div id=\"original\"></div></section><section class=\"panel\"><h3 data-i18n=\"resolved\"></h3><div id=\"resolved\"></div></section><section class=\"panel\"><h3 data-i18n=\"steps\"></h3><div id=\"steps\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"fail\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#original,#resolved{font:16px/1.8 var(--viz-font-mono);min-height:3.6em;overflow-wrap:anywhere}#steps{font:12px/1.8 var(--viz-font-ui);white-space:pre-line;min-height:9em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}const constraints=[['α',['List','β']],['β','Int'],['β',['Fn','α','Bool']],['α','Bool']],isVar=x=>typeof x==='string'&&['α','β'].includes(x);function render(x){return Array.isArray(x)?x[0]+'('+x.slice(1).map(render).join(', ')+')':x}function draw(){let subst={},log=[],error='';function apply(x){return isVar(x)&&subst[x]?apply(subst[x]):Array.isArray(x)?[x[0],...x.slice(1).map(apply)]:x}function occurs(v,x){x=apply(x);return x===v||Array.isArray(x)&&x.slice(1).some(c=>occurs(v,c))}function unify(a,b){a=apply(a);b=apply(b);if(JSON.stringify(a)===JSON.stringify(b))return;if(isVar(a)){if(occurs(a,b))throw Error('occurs check: '+a+' in '+render(b));subst[a]=b;return}if(isVar(b))return unify(b,a);if(Array.isArray(a)&&Array.isArray(b)&&a[0]===b[0]&&a.length===b.length){a.slice(1).forEach((c,i)=>unify(c,b[i+1]));return}throw Error('conflict: '+render(a)+' ≠ '+render(b))}try{constraints.forEach(([a,b],i)=>{if(!$('#c'+(i+1)).checked)return;unify(a,b);log.push(render(a)+' = '+render(b)+' ✓')})}catch(e){error=e.message;log.push(error)}const original=['Fn','α',['List','β']];set('original',render(original));set('resolved',error?'⊥':render(apply(original)));set('steps',log.join('\\n')||'∅');set('explanation',t(error?'fail':'ok'));$('#resolved').dataset.valid=String(!error);$('#resolved').dataset.substitution=error?'{}':JSON.stringify(Object.fromEntries(Object.keys(subst).map(k=>[k,apply(subst[k])])));}document.querySelectorAll('input').forEach(e=>e.onchange=draw);draw();","audio":false,"strings":{"c1":"α = List(β)","c2":"β = Int","c3":"β = Fn(α, Bool)","c4":"α = Bool","fail":"A constructor conflict or infinite type makes this solve fail; no partial type is returned.","heading":"Where must a substitution propagate to solve a type?","intro":"Select constraints; the solver reunifies the selected set. Observe substitution through the original type; failed solves leave no partial result.","ok":"Selected constraints have a consistent substitution; unconstrained variables remain unknown.","original":"Original type","reset":"Reset","resolved":"After applying all substitutions","scope":"Finite first-order Fn/List/Int/Bool types, no subtyping or recursive types. Selection rebuilds a solve in fixed order; not full Algorithm W.","scopeLabel":"Model scope and assumptions","steps":"Constraint processing"}}
Hindley–Milner: Generalization and Instantiation Are Both Essential
In a simplified pure HM language, let id = fun x -> x can be inferred as α→α, and at the let binding, type variables not free in the outer environment are generalized into a type scheme ∀α. α→α.
Each use of id re-instantiates it with fresh type variables. Thus, id 1 and id true can use Int→Int and Bool→Bool respectively, rather than sharing the same unbound variable across two calls.
Lambda parameters do not undergo such let generalization in basic HM. If fun f -> (f 1, f true) were to treat f as a polymorphic function callable on two types, it would exceed this simple rule; one cannot claim that arbitrary higher-order polymorphism of function parameters is automatically inferred by HM.
Languages with effects like mutable references need to restrict generalization, such as the value restriction in ML-family languages, to avoid incorrectly treating shared mutable cells as multiple types. Actual languages may go beyond pure HM, adding type classes, subtyping, or local annotations, changing the inference algorithms and guarantees accordingly.
Generalization Cannot Turn Unknowns From the Outer Environment Into Arbitrary Types
Suppose the environment already has x:?a, and let keep = fun u -> x has type ?b→?a. ?b can be generalized, but ?a comes from the environment and remains constrained by the actual type of x. Therefore, the scheme is ∀β. β→?a, not ∀αβ. β→α. The latter scheme incorrectly allows callers to arbitrarily choose the return type, as if the same x could be both Int and Bool.
The set of generalized variables is typically written as free(type) − free(environment); instantiation only refreshes scheme-quantified variables, preserving associations with environment unknowns. The unifier above only resolves equality constraints and does not implement this step, so it cannot be directly called a complete Algorithm W.
Shared mutable cells make the problem more obvious: if a storage location is incorrectly generalized, each access re-instantiates it to an arbitrary element type, potentially writing as Int first and then reading the same content as a function type. OCaml uses weak type variables and rules like the value restriction to constrain this, with specific relaxation rules leveraging variance. OCaml Polymorphism and Mutable State
Which unknowns may be fresh at each use?
The set difference in generalization has a concrete purpose: an unknown inherited from the environment cannot be chosen independently by every caller. id has no such dependency; keep returns the same outer x, so different argument types still share its result type. Two instances expose both fresh quantified variables and preserved environment variables.
Q=FTV(τ)∖FTV(Γ),σ=∀Q.τ
Preparing the visual
Which unknowns may be fresh at each use?
Generalize only variables free in the type but not the environment. id instances are independent; keep has independent argument types but shares x’s result type.
{"id":"hm-generalization","title":"Which unknowns may be fresh at each use?","summary":"Generalize only variables free in the type but not the environment. id instances are independent; keep has independent argument types but shares x’s result type.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"id\" data-i18n=\"identity\"></button><button data-mode=\"keep\" data-i18n=\"keep\"></button></div><label class=\"form-field\"><span data-i18n=\"external\"></span><select id=\"external\"><option>Int</option><option>Bool</option></select></label><p id=\"source\" class=\"source\"></p><section class=\"panel\"><h3 data-i18n=\"free\"></h3><div id=\"free\"></div></section><div id=\"instances\" class=\"table\"></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#source{min-height:3.6em}#free{font:13px/1.8 var(--viz-font-ui);white-space:pre-line;min-height:5.4em}#instances .tr{grid-template-columns:1fr 1fr}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}let mode='keep';function draw(){const x=$('#external').value;$('#external').disabled=mode==='id';set('source',mode==='id'?'let id = fun u → u':'x : α\\nlet keep = fun u → x');set('free',mode==='id'?'FTV(type) = {α}\\nFTV(Γ) = ∅\\n∀α. α → α':'FTV(type) = {α,β}\\nFTV(Γ) = {α}\\n∀β. β → α');const outputs=mode==='id'?['Int','Bool']:[x,x];table('instances',[[mode==='id'?'α₁ → α₁':'β₁ → α','Int → '+outputs[0]],[mode==='id'?'α₂ → α₂':'β₂ → α','Bool → '+outputs[1]]]);set('equation',mode==='id'?'Q = {α} − ∅ = {α}':'Q = {α,β} − {α} = {β}; α = '+x);$('#instances').dataset.outputs=outputs.join(',');pressed(mode);}document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;draw()});$('#external').onchange=draw;draw();","audio":false,"strings":{"external":"Type of outer x","free":"Free variables and generalization","heading":"Which unknowns may be fresh at each use?","identity":"id: return argument","instances":"Two call instances","intro":"Compare id with keep capturing x. Calls use Int and Bool; change x’s type and observe whether both result types move together.","keep":"keep: return outer x","mode":"Binding form","reset":"Reset","scope":"Pure HM let bindings, without mutable references, value restriction, subtyping or polymorphic parameters. The environment fixes x’s type.","scopeLabel":"Model scope and assumptions"}}
Subtyping and Variance: Which Substitutions Are Safe
If Dog<:Animal, a position requiring only Animal capabilities can accept Dog. For functions, a function that can handle any Animal can be used by a caller that only passes Dog; input positions exhibit contravariance, while output positions typically exhibit covariance.
Read-write containers that allow Box<Dog> to be directly used as Box<Animal> could allow users to write a Cat and then read it from the original Dog view, so they usually need to be invariant, or use other designs like runtime checks or restricted interfaces.
Covariance for read-only interfaces presupposes that there is indeed no capability to write incompatible values through that interface. Variance is determined by how type parameters are used in each position; you cannot draw a blanket conclusion just by seeing the names List or Array.
Rust's subtyping mainly revolves around lifetimes and higher-rank lifetime types, and type constructors have their own variance; it cannot be simplified to "everything else is only compared by name." Trait implementation and type coercions are also not equivalent to ordinary inheritance subtyping. Rust Subtyping and Variance
Why does function input variance run backward?
Read function types as set contracts. Possible caller inputs must be contained in the candidate’s accepted inputs; possible candidate outputs must fit the caller’s permitted results. Caller input is Dog, while candidate input/output and required output vary. Gold values exhibit an uncovered input or an out-of-contract result.
(Ac→Rc)<:(Ae→Re)ifAe<:Ac∧Rc<:Re
Preparing the visual
Why does function input variance run backward?
Accepting Animal covers Dog inputs; returning Dog meets an Animal result contract. Candidate input must cover caller inputs, while candidate output must fit the allowed result set.
{"id":"function-variance","title":"Why does function input variance run backward?","summary":"Accepting Animal covers Dog inputs; returning Dog meets an Animal result contract. Candidate input must cover caller inputs, while candidate output must fit the allowed result set.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"form-field\"><span data-i18n=\"input\"></span><select id=\"input\"><option>Animal</option><option>Dog</option><option>Cat</option></select></label><label class=\"form-field\"><span data-i18n=\"output\"></span><select id=\"output\"><option>Animal</option><option>Dog</option><option>Cat</option></select></label><label class=\"form-field\"><span data-i18n=\"required\"></span><select id=\"required\"><option>Animal</option><option>Dog</option><option>Cat</option></select></label><section class=\"panel\"><h3 data-i18n=\"incoming\"></h3><div id=\"incoming\"></div></section><section class=\"panel\"><h3 data-i18n=\"outgoing\"></h3><div id=\"outgoing\"></div></section><p id=\"equation\" class=\"formula\"></p><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#incoming,#outgoing{display:grid;grid-template-columns:1fr 1fr;gap:8px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}const values={Animal:['Dog','Cat'],Dog:['Dog'],Cat:['Cat']};function draw(){const a=$('#input').value,b=$('#output').value,required=$('#required').value,inputOK=values[a].includes('Dog'),outOK=values[b].every(x=>values[required].includes(x));cells('incoming',['Dog','Cat'].map(x=>({text:x+(x==='Dog'?' ← caller':''),cls:x==='Dog'?(inputOK?'on':'gold'):'empty'})));cells('outgoing',['Dog','Cat'].map(x=>({text:x+(values[b].includes(x)?' ← result':''),cls:values[b].includes(x)?(values[required].includes(x)?'on':'gold'):'empty'})));set('equation','Dog <: '+a+' '+(inputOK?'✓':'✗')+'; '+b+' <: '+required+' '+(outOK?'✓':'✗'));set('explanation',t(inputOK&&outOK?'ok':'bad'));$('#equation').dataset.valid=String(inputOK&&outOK);}$('#input').onchange=draw;$('#output').onchange=draw;$('#required').onchange=draw;$('#output').value='Dog';draw();","audio":false,"strings":{"bad":"An inclusion fails; an uncovered input or excess output is a counterexample.","heading":"Why does function input variance run backward?","incoming":"Input: what may the caller send?","input":"Candidate input type","intro":"The caller sends only Dog; choose its required result type. Change the candidate input and output to test both directions of substitution.","ok":"Both inclusions hold: the candidate satisfies this caller contract.","outgoing":"Output: what may the caller receive?","output":"Candidate result type","required":"Caller’s required result type","reset":"Reset","scope":"Finite nominal Animal={Dog,Cat}; Dog and Cat are unrelated subtypes. Plain function subtyping; no effects, mutable containers, lifetimes or conversions.","scopeLabel":"Model scope and assumptions"}}
Generic Semantics and Machine Code Strategies Are Separate
Strategy
Core Approach
Costs and Opportunities
Monomorphization / Specialization
Generate code per instance
Opportunities for specialized optimization, compilation and size costs
Erasure
Implement some generic semantics with shared representation
May require conversions, boxing, and runtime checks
Dictionary or Witness Passing
Pass implementations of required operations
Shared code and indirect calls, still optimizable
Languages and compilers can combine strategies. One cannot assert that all generics in Swift or other languages are only monomorphized, nor can one say that all type information in Java becomes literal List<Object>; erasure rules, bounds, and retained metadata have specific specifications.
Specialization reduces certain indirect calls, but it does not mean generic programs have "zero runtime overhead" overall. Allocation, actual algorithms, and code caching still exist; compilers may also merge equivalent instances.
The constraint T supports a certain operation means checking conditions such as required implementations and associated types. Solving may recursively generate new obligations, involving consistency, ambiguity, and termination strategies. Static generic calls and dynamic interface objects have different dispatch mechanisms.
Not every trait can be directly written as a valid dyn Trait; object compatibility, associated types, and method signatures have requirements. Understanding requires first distinguishing between "checking that T has the capability" and "how to find the implementation at runtime."
Error Messages Are Also Part of Type System Implementation
Preserve sources for constraints: which argument, which return position, which declaration produced the expected type. Error recovery can use tagged unknowns or error types to reduce cascading errors, but it must not treat them as proven correct during successful code generation.
Actual inference also requires resource limits and clear annotation entry points to avoid extremely long compilations caused by complex instances. Whether adding language features remains decidable depends on analyzing the combined rules, not just judging by feature names.