Skip to main content
A #[module] lowers each step call in its body to a node in a program graph. Lines that are not step calls, plain Rust like uppercasing a string, cannot be described as steps, so DSRs keeps each one as a hole: a named, typed black box. The graph records the hole’s name, its inputs, and its output type; the interior stays private. A typed border around an unknown interior is still a contract, like a blender: you do not know the circuitry inside, but you know what goes in and what pours out, and that is enough to build the rest of the program around it.

Declaring a hole

A type-ascribed let becomes a hole. That is the whole mechanism:
drafter and checker are steps. upper is not a step call, so it becomes a hole named upper. Its input is drafter.draft, found automatically, and its output is a String.

Allowed types

The ascribed type becomes a field in the program’s interface, so it must be simple:
  • String
  • integers (i64, i32, and friends)
  • floats (f64, f32)
  • bool
  • Vec<...> and Option<...> of the above
Anything else is a compile error with a hint to pick one of these.

Type anchors

Values that flow into a hole from earlier steps arrive through a decode step at run time, and the compiler sometimes cannot work out their concrete type on its own. The fix is a small inner let with a type, as an anchor:
The anchor must be a type-ascribed let. Calling a method on the incoming value instead, for example ticket.clone(), fails inference, because the method resolves against a type the compiler does not yet know. let t: String = ticket; states the type first, and everything after it infers normally.

Two kinds of holes

The examples below use a hole named clean that scrubs email addresses out of a ticket string.

Native holes

A hole written in a #[module] body is compiled Rust, and machine code cannot travel in a text artifact. The printed program therefore records only the typed border and a fingerprint of the code, marked extern:
Whoever loads this file must supply an implementation for clean: a host binds its native holes by name, and the 16-hex fingerprint lets it verify it holds the same code the file was printed against, not merely something with a matching name. Next to the binary that defined it, the program runs. Loaded anywhere else, it fails immediately with a “host hole unbound” error.

Sandboxed holes

A sandboxed hole carries its implementation inside the file, as JavaScript, executed in a sandbox by whatever host loads it:
Same typed border, but the interior travels with the file. The portability rule: a file with only steps and sandboxed holes runs anywhere; a file with a native hole runs only beside the binary that defined it. The file tells you which kind you are holding.

Inspecting holes

Every module lists its holes in a constant called OPACITY. For a module frontdesk with the clean hole above:
Each entry carries the hole’s name, its kind, the actual source excerpt, and the reason it could not be lowered to a step. Nothing becomes opaque in silence.

Forbidding holes

deny_holes makes any line that is not a step call a compile error:

The optimization cost

Holes are opaque to optimizers. An optimizer can rewrite an instruction because an instruction is data with an address; it cannot rewrite the inside of a hole, because there is nothing there to read. Every hole subtracts from the surface an optimizer can improve. Prefer fewer and smaller holes: each line moved out of a hole into a visible step is a line an optimizer can tune and any host can run. Keep the interiors small enough that what stays opaque is genuinely the part with nothing to say.

Common mistakes

No type on the let. A hole must declare its output type. let upper = ...; without a type is a compile error that tells you to write let upper: String = ...;. Using a whole step result inside a hole. Holes consume output fields, not whole results. Write drafter.draft, not drafter. Expecting the file to carry the code. It does not. The printed program marks a native hole as extern. Portability stops at the binary that defined it. If you need a fully portable program, replace the native hole with a sandboxed hole in the .dsrs file, or with another step.

See also

  • The module macro for the body rules that decide what becomes a step and what becomes a hole
  • The .dsrs file for both hole forms in the grammar
  • Capabilities for the caps [] printed on every hole
  • Runtime for binding native holes and the load-time checks
  • Optimizers for the tuning surface that holes subtract from