skip to content

Forbidden Runtime Operations

The concrete restrictions that follow from losing the argument: no runtime test, no construction, no array of it, and overloads that collide. Interviewers ask you to derive the compile error.

on this pageshow

questions

4

Why does a language that discards type arguments reject a run-time test asking whether a value is a stack of text edits?

level: juniorimportance: must knowfreq 62%

answer

  1. a test reads only what survives
  2. the value, not the declaration
  3. one compiled shape per container
  4. no evidence left to compare
  5. only the argument-free test decides

basics

~20 s

A run-time test can only read what the value still carries, and the discarded argument is not there. Every stack of edits shares one run-time shape, so the test cannot be decided, and the compiler rejects it instead of guessing.

solid answer

~50 s

A run-time type test asks the value itself what it is, so it can only answer from the description the value still carries while the program runs. Where a compiler discards type arguments, the object that remains records that it is an undo stack and nothing about the kind of edit it was declared to hold: the same compiled shape backs a stack of text edits and a stack of style edits. The parameterized half of the test therefore has no evidence to consult. The compiler refuses it at compile time rather than let it produce a verdict nothing supports. The argument-free half of the same test - `is this an undo stack at all` - stays decidable, because the container shape does survive. Platforms that keep arguments available at run time allow the full test precisely because the evidence is still attached.

code

pseudocode · 6 lines
pseudocode
function replayIfTextEdits(value):
    if value is an UndoStack:            # decidable - the container shape survives
        replayLoosely(value)

    if value is an UndoStack of TextEdit:  # rejected - the argument is gone
        replayStrictly(value)

go deeper

for a junior

Remember the shape of the rule: a run-time test reads only what the value still carries, and a discarded type argument is not part of that. Do not expect an object to remember what it was declared to hold.

for a middle

Explain the mechanism rather than the rule: one compiled container shape backs every argument, so the parameterized half of the test has nothing to consult and is refused at compile time instead of returning an unsupported verdict.

for a senior

Show where this bites in real code - a boundary that receives a container and wants to branch on its element type - and how you restructure so the decision is taken where the type is still known rather than interrogated out of a value.

for a principal

The judgment to own is whether your codebase leans on element-type branching at boundaries at all. A design that asks a container what it holds is fragile under either compilation model, and standardising the boundary shape is cheaper than arguing about the test.

## What a run-time type test actually consults A **run-time type test** hands a value to the runtime and asks the value what it is. That is the whole mechanism: the answer comes from the description the value carries with it while the program runs - not from the source text that produced it, and not from the variable it happens to be sitting in. A test is decidable exactly to the extent that the carried description is complete enough to settle it. Two things must be kept apart from the start: - **The declaration** - `an undo stack whose elements are text edits` - is source text the compiler reads, checks and then throws away or keeps, depending on the platform. - **The carried description** - what the allocated stack object still says about itself at run time. In a platform that discards type arguments these two are not the same thing, and the gap between them is this whole subject. ## What is left of `stack of text edits` The declaration glues two claims together: a **container shape** (an undo stack) and a **type argument** (the kind of edit it holds). The discarding model keeps the first and drops the second. One compiled body serves every argument, so a stack declared for text edits and a stack declared for style edits allocate objects of the same run-time type. The test therefore splits in half: 1. **Is this an undo stack?** Answerable - the container shape is part of what the value carries. 2. **Are its elements text edits?** Not answerable - nothing about the value records that. The first half is not a consolation prize; it is the only part that was ever backed by evidence. ## Why the elements cannot answer it either The obvious rescue is to look inside: walk the stack and inspect what is actually stored. It does not work, and seeing why is what separates a memorised rule from a derived one. - An **empty stack** has nothing to inspect, yet it still has a declared element type, so the inspection cannot even be attempted. - The argument is a claim about **every element the stack will ever hold**, not only the ones present now. Inspecting what is stored proves nothing about the next push. - A stack that reached this code through a step nobody verified may hold a **mixture**, and a mixture has no single element type to report. - Inspection costs a walk of the whole structure, so a test that reads as a constant-time question becomes proportional to size - and still returns a guess. ## Reject, or narrow Compilers in this model do one of two things, and it is worth knowing which form you wrote. | The test as written | What it needs to decide | Verdict when arguments are discarded | |---|---|---| | Is this an undo stack? | container shape only | decidable, allowed | | Is this an undo stack of text edits? | container shape plus the argument | rejected: the evidence was discarded | | Is this an undo stack of anything, argument unread? | container shape, argument deliberately unexamined | decidable, allowed | The third row matters. Writing the test so it never asks about the argument is legal, because it only asks what the value can still answer. What you cannot do is write the second row and expect the third row's reliability. ## The undo stack at a boundary An editor exposes an extension point: a plug-in hands back an undo stack and the editor wants to replay it, but only if it is a stack of text edits. The test it wants to write is exactly the rejected one. The fix is structural, not clever: - Have the boundary accept a **declared** shape, so the check happens where the argument is still known - at compile time, at the call. - Make the distinction something the value genuinely carries, such as a **distinct container type per edit family**, so the argument-free test is enough. - Move the decision to the caller that still has the static type, instead of interrogating a value that has lost it. ## Where platforms differ This restriction is a property of a compilation strategy, not of generics as an idea. Some platforms keep type arguments available while the program runs, and there the full test is decidable and permitted. Others discard them, and there it is not. Before claiming that such a test works, name which model you are in - an engineer who says 'that check works fine' without saying which model is describing one platform and generalising it to all of them.

  • What weaker test is still decidable on the very same object?
    The one that ignores the argument: asking whether the value is an undo stack at all. The container shape is part of what the value carries at run time, so that question can be answered honestly. It simply cannot tell a stack of text edits from a stack of style edits, which is the distinction the original code wanted.
  • Why is the restriction on the test, when a cast to the same parameterized type is often accepted?
    A test must produce a truthful yes or no right now, and the evidence for the parameterized half is gone, so there is nothing to answer with. A cast is an instruction to treat the value as that type; the compiler can let it through while recording that it is no longer checking. One demands a verdict, the other only demands a description.

It is like a crate stamped only 'crate'. You can confirm it is a crate, but the label saying what it was packed for was removed before shipping, and looking at whatever happens to be inside today does not restore it.

saying these in an interview costs you the question

  • Says the object remembers the argument it was created with.
  • Claims the test works and only needs a cast afterwards.
  • Thinks the compiler substitutes the variable's declared type at run time.
  • Believes the test quietly answers false instead of being rejected.
  • Assumes every platform forbids this test, model regardless.
open as a page

Inside a generic undo stack, why can the body not construct a fresh value of its own type placeholder?

level: middleimportance: must knowfreq 55%

basics

~20 s

Construction has to name a concrete type at the point it happens: what to allocate and how to initialise it. A placeholder names none of that once the argument is discarded, and nothing promises the argument can be built at all.

open as a page

Why do two undo-stack operations that differ only in their type argument fail to compile as separate overloads?

level: middleimportance: should knowfreq 46%

basics

~20 s

Once the argument is discarded, both operations expose the same parameter shape, so the two declarations collapse into one signature. The clash is a duplicate declaration, reported where the operations are declared rather than at any call.

open as a page

An undo stack wants an array of its edit placeholder for fast replay, so why is creating that array directly rejected?

level: seniorimportance: should knowfreq 38%

basics

~20 s

An array keeps its element type while the program runs and guards every store against it, but the type argument is the one thing compilation discarded. The creation would have to record an element type that no longer exists, so it is refused.

open as a page