A chat client's remote-service handle is synthesized at run time from a declared contract: what does each generated member body do?
answer
- no hand-written bodies anywhere
- one object behind every member
- the call becomes data
- member description plus argument list
- handler's return becomes the result
basics
~20 sEvery generated member body does the same thing: it packages a description of the member that was called together with the argument list, hands both to one handler object, and returns whatever that handler returns.
solid answer
~40 sThe synthesized type declares exactly the members the contract declares, so a caller holding a contract-typed reference uses it unchanged. None of those bodies holds real logic. Each builds a description identifying which member was invoked - its declaration, name and parameter list - collects the arguments into one list, and calls a single `handler` with both. The handler's return value becomes the call's result, converted to the member's declared return type. So dispatch moves out of the type system and into a conditional inside the handler: one piece of code serves every member and picks behaviour from data at run time. For a remote-service handle there is no local target at all - the handler encodes the member description and the arguments, sends them, and decodes the reply.
code
pseudocode · 14 lines// the shape every generated member body has
function sendMessage(text, room) {
return handler.handle(self, describe("sendMessage", 2), [text, room])
}
function history(room, limit) {
return handler.handle(self, describe("history", 2), [room, limit])
}
// one handler behind all of them - no local target exists
function handle(standIn, member, args) {
request = encode(member.name, args)
reply = transport.send(request)
return decode(reply, member.declaredReturn)
}go deeper
Recall that a usable object can be produced while the program runs from a declared contract alone, and that calling one of its members does not run code anybody wrote for that member.
Explain the funnel end to end: each generated body passes a description of the invoked member plus the argument list to one handler, whose return value is converted to the declared return type.
Show where it bites in production. The handler is checked by nothing, so a wrong return type, an unrecognised member or a swallowed failure surfaces only under traffic, at the stand-in's boundary.
Weigh what the team trades away - build-time dispatch and the compiler's own checking - against never hand-writing another forwarding body for a contract that keeps growing.
## What is actually built A stand-in synthesized at run time is a **type that did not exist when the program was compiled**. Two things go in: a **declared contract** - a named set of member signatures with no bodies - and one **handler** object. What comes out is a new type whose declared supertypes include that contract, plus an instance of it. The caller's variable is typed by the contract, so the caller cannot tell that the object it holds was invented a few microseconds earlier. That is the shape behind a chat client's handle to a remote service. Nobody wrote a class for that handle. The program knows the contract the service publishes - `sendMessage`, `history`, `close` - and on start-up it asks for an object that satisfies it. ## What each generated body contains Every member the contract declares gets a body, and **all the bodies have the same shape**: - they contain no logic belonging to the member they implement; - they build a **description of the member that was called** - which declaration it is, its name, its parameter list, its declared return type; - they collect the arguments into a single list, whatever their number or type; - they call one method on the single `handler` the stand-in was built with, passing that description, the arguments, and usually the stand-in itself; - they return whatever the handler returned, converted to the member's declared return type. The consequence is the point of the mechanism: **behaviour is chosen from data at run time**, not from code at build time. A contract with forty members produces forty generated bodies and still exactly one place where you write what a call means. ## What the handler is handed, and why | It receives | Why it needs it | |---|---| | The member description | The only thing that says *which* member was called; two members sharing a name differ only by parameter list | | The argument list | The values, in declaration order, with nothing pre-interpreted | | The stand-in instance | So one handler serving many stand-ins can tell which one the call arrived on | | Whatever it closed over | A target, a connection, a policy - the handler holds these, the stand-in does not | Note the last row. **The stand-in holds no target.** It holds a handler; the handler may hold a target, or none at all. The remote-service handle is the case with no target: the handler encodes the member name and arguments into a request, sends it, waits, and decodes the reply. Nothing local implements `sendMessage` anywhere. ## The return trip, and where checking moved 1. The handler returns a value typed loosely - usually as the most general type available, because one signature must serve every member. 2. The generated body converts that value to the member's **declared** return type and hands it back to the caller. 3. If the value does not fit, the call fails **at run time, at the stand-in's boundary**, far from where the handler was written. That is the trade, stated in the direction that matters. The **caller** is still checked at build time against the contract, because it only ever sees contract-typed members. The **handler** is checked by nothing: a member declared to return a result and a handler that returns nothing are each well-formed on their own and meet only under traffic. Failures work the same way - an exception the handler raises reaches the caller only if the member's declared signature permits that type; otherwise the runtime substitutes a failure of its own and the original is easy to lose. ## Why one handler rather than generated per-member code Both are possible; funnelling is the common design, for practical reasons: - **The contract can grow.** A member added next month gets a generated body automatically, and the call reaches the handler with a description it has never seen. Nothing fails at build time, so a handler that fails loudly on an unrecognised member is the difference between a bug found in minutes and one found in a week. - **The policy is uniform.** Timing, retrying, encoding, recording - written once, applied to every member because every member arrives through the same door. - **Dispatch becomes a conditional.** What the type system used to do - pick an implementation from the static type at the call - is now a comparison inside your code. That is the cost: the lookup in the handler does a job the compiler did for free, and it is only as correct as you wrote it. ## What this mechanism is not It does not edit the target and it does not extend its class. It builds a **new type from a contract's declarations**, and everything about the call - the selected member, the argument values, the result - passes through data structures your handler reads. Where no contract exists to build from, this mechanism has nothing to work with and a different kind of stand-in is required.
- Two members of the contract share a name and differ only in their parameter lists - how does the handler tell which was called?From the description it is handed, which identifies the declaration rather than just the name: it carries the parameter list, and usually an identity the runtime hands you for that declaration. Matching on the name alone conflates the two, which is a common handler bug and shows up as one overload quietly getting the other's behaviour.
- What happens when the handler returns a value the called member's declared return type does not accept?The call fails at run time, at the stand-in's boundary. The caller was checked against the contract when it was compiled, but nothing checked the handler - its signature is deliberately loose so one method can serve every member. Members that declare a result the runtime cannot produce from an absent value are the usual offenders.
- The contract gains a new member after the handler was written - what happens?The synthesized type still satisfies the contract, because it is built from the contract as it exists while the program runs. The new member gets a generated body like every other, and calls to it reach the handler with a description it has never seen. The handler decides at run time what that means. Nothing warns you earlier - that is the price of moving dispatch into data.
A building switchboard: every extension is wired to the same operator, who decides what each call means. The extensions carry no logic of their own; the operator carries all of it.
saying these in an interview costs you the question
- Thinks the stand-in extends the target's class to satisfy the contract
- Says the stand-in holds the target and forwards without any handler
- Believes each contract member gets its own hand-written body
- Assumes the compiler checks what the handler returns
- Claims a stand-in can only wrap an existing object, never replace one