The unknown type

unknown is CruftScript's type for a value whose type you do not yet know, and its only catch-all: any value goes in, but you must check what it is with a runtime guard before you can use it. There is no any and no cast that skips the check, so unverified data cannot flow into typed code unchecked.

unknown is CruftScript's type for a value whose type you do not yet know. It is the one place the type system lets you hold something without a specific type, and it is deliberately hard to misuse: you can put any value into an unknown, but you cannot do anything with an unknown until you have checked what it actually is.

If you have used TypeScript, this is the same idea as its unknown, made the only option. The escape hatch TypeScript also offers — any, a value the type checker stops reasoning about entirely — does not exist in CruftScript. There is no any keyword to type, so the "turn the checker off for this value" move is simply not available. unknown is the sole catch-all.

Where you meet it

You get an unknown whenever a value arrives without a guaranteed type. The most common source is data entering your program from outside it — a parsed JSON payload, a request body, a message from another thread, the result of a call into untyped code. That data has not been verified yet, so the type system hands it to you as unknown and makes you check it before you trust it.

const payload: unknown = parseIncoming(raw)
payload.id        // error: payload has no known shape yet

The one rule

Every type is assignable to unknown — anything fits into it — but unknown is assignable to nothing except another unknown. In plain terms: putting a value in is free; taking a usable value back out requires a check.

let box: unknown = 42          // ok, anything goes in
let n: number = box            // error: unknown is not a number until you prove it

To get a real type out, you narrow the value with an ordinary runtime check — the same checks you would write in JavaScript — and inside the branch where the check passes, the value has the narrowed type:

if (typeof payload === "string") {
  payload.toUpperCase()        // here payload is a string
}

The checks that narrow an unknown are typeof, instanceof, in, and user-written type-guard functions. See type narrowing for the full set.

No casting your way out

In many languages you can force a value to a type with a cast and move on. CruftScript does not let you cast an unknown into a specific type on your word alone. A cast to a concrete type inserts a real check at runtime; a cast that has no way to be checked is rejected outright. The classic TypeScript double-cast escape, value as unknown as SomeType, is a compile-time error here rather than a silent override. The only honest way out of unknown is a check that actually runs.

unknown has no keys

Because an unknown value could be anything, it exposes nothing to index into. Asking for the set of its keys gives you never — the empty set — rather than "some string or symbol." This keeps type-level code that maps over an unknown's keys from silently producing a meaningless result; it produces nothing instead.

type K = keyof unknown         // never (no keys)

Why it works this way

The point of unknown is that a value you have not verified should be impossible to use as if you had. Most type systems leave a gap here — an any, an unchecked cast, a non-null assertion — through which unverified data can flow into typed code and cause a failure far from where it entered. CruftScript closes that gap by making the only catch-all a value you are forced to check. It trades a little convenience for the guarantee that a typed value has actually been confirmed to be that type.

Limitations

  • You must narrow before every use. There is no shortcut. If a value is unknown, you check it, every time, before you can call methods on it or assign it to a typed binding. This is the intended cost of the guarantee.
  • Narrowing is limited to real runtime checks. Only typeof, instanceof, in, and validated type-guard functions narrow an unknown. There is no compile-time-only assertion that skips the check.