DriftScript
Documentation

Reference

Diagnostics

The compiler talking, captured by compiling a program per entry rather than transcribed.

The compiler can emit 144 distinct codes. 13 of them are catalogued here, across 2 families, and every one was produced by compiling the program shown above it rather than transcribed from the source.

That distinction matters more than it sounds. A message is built where it is raised, from the name it is about, so a transcribed catalogue would be a template with a hole in it and would go stale the first time anybody reworded one. A snippet here that stops producing the code it is filed under fails the build.

DS0201 resolution and types

// Writing through a parameter that is not `mut`.
//
// Mutability sits on the parameter rather than on the type, so a signature says which of its
// arguments it may write and the compiler holds it to that.

data Door {
    open: bool = false
}

fn shut(door: Door) {
    door.open = false
}
DS0201.drs:11:5  DS0201  `door` is not declared `mut`, so it cannot be written through
      door.open = false
      ^^^^^^^^^

DS0205 resolution and types

// A name that is not defined.
//
// Functions may be called before they are declared, so declaration order carries no meaning and
// this is genuinely a missing name rather than one that arrives later in the file.

fn go() -> f32 {
    return missing(1)
}
DS0205.drs:7:12  DS0205  `missing` is not defined.
      return missing(1)
             ^^^^^^^

DS0210 resolution and types

// Deliberately refused.
//
// A `match` must cover every variant, and the error names the ones it missed rather than saying
// "not exhaustive", which is the answer you were about to go and look up. `Amber` is missing here,
// and the alternative to this error is a run-time surprise on the one afternoon the light is amber.

enum Light {
    Red
    Amber
    Green
}

fn go(light: Light) -> bool {
    return match light {
        Red => false
        Green => true
    }
}
exhaustive.drs:14:12  DS0210  this `match` does not cover `Amber`
      return match light {
             ^^^^^^^^^^^^^^^^^^^^^^^^

DS0226 resolution and types

// `Ok` with nothing to say what the error type is.
//
// `Ok` and `Err` take their other half from context: the binding's annotation or the function's
// return type. With neither, this is an error rather than a guess.

fn go() {
    let a = Ok(1)
}
DS0226.drs:7:13  DS0226  `Ok` needs a `Result` type from its context — annotate the binding or the function's return type
      let a = Ok(1)
              ^^^^^

DS0228 resolution and types

// A record literal that leaves a field out.
//
// Leaving one out is an error rather than a silent fill from the declared default. A literal that
// looks complete while half of it came from somewhere else is the same ambiguity as an implicit
// null, one level up. Defaults are for the generated `createDoor()`, which is a different
// operation with a different name.

data Door {
    open: bool = false
    angle: f32 = 0
}

fn make() -> Door {
    return Door { open: true }
}
DS0228.drs:14:12  DS0228  `Door` needs a value for `angle`
      return Door { open: true }
             ^^^^^^^^^^^^^^^^^^^

DS0230 resolution and types

// Deliberately refused.
//
// There is no implicit widening. The compiler names the conversions rather than guessing which
// one was meant, because the guess is where a value quietly changes width.

fn add(a: u8, b: u32) -> u8 {
    return a + b
}
widening.drs:7:12  DS0230  `+` needs both operands to be the same type, but found `u8` and `u32`. There is no implicit widening; convert one explicitly with `checked`, `clamp` or `wrap`.
      return a + b
             ^^^^^

DS0231 resolution and types

// A wrapping operator on a float.
//
// `+%` and `+|` need an integer. On an `f32` this is an error rather than a synonym for `+`,
// because floats neither wrap nor saturate, and accepting it would teach that the distinction is
// decorative.

fn add(a: f32, b: f32) -> f32 {
    return a +% b
}
DS0231.drs:8:12  DS0231  `+%` is wrapping or saturating arithmetic and needs an integer, but this is `f32`
      return a +% b
             ^^^^^^

DS0251 resolution and types

// A function that can finish without returning.
//
// A function declaring a return type must return on every path. A trailing `if` without an `else`
// is an error, because the path where the condition is false reaches the end.

fn pick(on: bool) -> f32 {
    if on {
        return 1
    }
}
DS0251.drs:6:1  DS0251  `pick` declares `f32` but can finish without returning
  fn pick(on: bool) -> f32 {
  ^^^^^^^^^^^^^^^^^^^^^^^^^^

DS0254 resolution and types

// Deliberately refused.
//
// A bare value does not flow into an option position. `return 1` from a function returning `f32?`
// is an error, and `return some(1)` is what was meant. A language that accepted the first has
// reinvented implicit null in the other direction: a reader can no longer tell an absence that
// was decided from one that was never filled in.

fn find(present: bool) -> f32? {
    if present {
        return 1
    } else {
        return none
    }
}
option.drs:10:16  DS0254  this function returns `f32?` but this is `f32`
          return 1
                 ^

DS0255 resolution and types

// Deliberately refused.
//
// There is no truthiness. A condition must be `bool`, and the fix is to say what you meant:
// `if count != 0`.

fn ready(count: u32) -> bool {
    if count {
        return true
    } else {
        return false
    }
}
truthiness.drs:7:8  DS0255  a condition must be `bool` but this is `u32`. There is no truthiness in this language; compare explicitly.
      if count {
         ^^^^^

DS0263 resolution and types

// Deliberately refused.
//
// A `List<Wolf>` is not a `List<Dog>`, even though a `Wolf` is a `Dog`. With covariance the call
// below would hand `adopt` a reference whose real list holds wolves, and pushing a plain `Dog` into
// it would put one in a list every reader believes is wolves.
//
// Record subtyping is sound in this language partly because that cannot happen: a record is read
// through its fields and a list can be written through.
//
// `:` before the brace is the base clause. It is not a new keyword, and a reader meets `:` here
// meaning "extends" and three lines later meaning "has type"; the position is what keeps the two
// apart, since a type annotation never follows a record's name.

data Dog {
    weight: f32
}

data Wolf: Dog {
    pack: u32
}

fn adopt(kennel: mut List<Dog>, arrival: Dog) -> u32 {
    push(kennel, arrival)
    return len(kennel)
}

fn release(pack: mut List<Wolf>, stray: Dog) -> u32 {
    return adopt(pack, stray)
}
invariance.drs:28:18  DS0263  `adopt` expects `List<Dog>` for `kennel` but this is `List<Wolf>`
      return adopt(pack, stray)
                   ^^^^

DS0288 resolution and types

// A system that writes a component it did not say it writes.
//
// `reads` and `writes` are assertions rather than documentation: the compiler works out what a
// system touches, following the functions it calls as well as its own body, and refuses a
// declaration that leaves a write out. The name of the system and the name of the component are
// both in the message, so the fix is where the reader is already looking.
//
// A declaration wider than the body is a warning instead, because claiming more than you touch is
// sometimes deliberate and costs only a scheduler that keeps two systems apart.

component Hunger {
    value: f32 = 0
}

system Feeder {
    reads Hunger

    update {
        for who in query<Hunger>() {
            who.Hunger.value = who.Hunger.value + 1
        }
    }
}
undeclared-write.drs:15:1  DS0288  `Feeder` writes `Hunger` and does not declare it. Add `writes Hunger`, or the engine refuses the write when the system runs.
  system Feeder {
  ^^^^^^^^^^^^^^^

DS0301 linking

// A module the target does not provide.
//
// This is the language's thesis as a single diagnostic. The file parses and type-checks; only
// linking declines it, and the refusal names the module, the target, and whether the capability
// exists in this host at all. That is what lets a file be written against a capability which has
// not shipped, and link unchanged on the day it does.
//
// This snippet named `drift/navigation` until 2026-08-28, when the engine provided it and this
// example stopped producing its own diagnostic. It named `drift/network` until 2026-09-03, when
// Track J provided that one and it happened again. The thesis demonstrating itself twice, which is
// a better argument than the paragraph above: nothing in the file changed either time, and it
// compiled on the day the host caught up.
//
// It named `drift/xr` until 2026-09-05, when Track I provided that one and it happened a third
// time. That was the last: this engine now describes every module the language specifies, so there
// is no unprovided surface left to point at and this snippet cannot be re-aimed again.
//
// So the target is what withholds the module now, rather than the engine being behind. That is the
// case a reader is actually in: a host chooses which capabilities its target manifest carries, and
// a file naming one it left out is refused with exactly this diagnostic. The demonstration is the
// same and it no longer depends on this engine having a hole.

import { session } from "drift/xr"

fn go() -> f32 {
    return 1
}
DS0301.drs:23:1  DS0301  `drift/xr` is not provided by target `driftengine`. The module is specified and your file is valid: add it to the target manifest if this host provides it, or it links when a host implements it.
  import { session } from "drift/xr"
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^