Generate platform-neutral RSScript. The language models ownership, retention, resource lifetime, and structured asynchronous control flow. It does not model host permission, deployment policy, OS authority, or provider implementation technology.
Never generate a file feature/profile header, a generic declaration-effect list,
an implementation-origin modifier, or a source-level unsafe marker. Use Dyn<P>
for dynamic protocol dispatch.
Implementation functions in .rss have bodies. Interface functions in .rssi
are ordinary bodyless declarations. Parameters use the closed read, mut, and
take data-effect set.
An .rssi file is supplied to the compiler alongside the implementation, not
copied into it. Never restate an interface declaration inside the .rss file
you are writing: its symbols are already in scope, and repeating one declares it
twice.
fn transform(input: take Image, options: read Options) -> fresh Image {
return Image.transform(input: take input, options)
}
If a call retains a parameter after return, add one structured clause per retained parameter:
fn Store.insert(store: mut Store, key: read String, value: read Value) -> Unit
retains(key)
retains(value)
{
// ...
}
Do not use source assertions for purity, allocation, blocking, panic behavior, parallelism, native implementation, or host service access. These facts are inferred or supplied by external provider metadata.
Two alternate spellings are accepted and desugared by the parser to the
canonical form. Both produce the same AST — the checker never sees the alternate
spelling — and rss fmt rewrites them to the canonical one, so formatting is
the normalizer. Prefer the canonical spelling when writing new code.
| Also accepted | Canonical (what rss fmt prints) |
|---|---|
T { field: value } |
T(field: value) |
Pattern => expr, |
Pattern => { expr } |
A trailing comma after a block arm (Pattern => { ... },) is accepted too.
Nothing else outside the generated grammar surface
is accepted: :: paths, task_group/with/select in expression position,
tuple destructuring in for, mut x: T = ... as a binding form, and
fn(x: T) -> U { ... } as a closure literal are all errors.
These four forms are the ones most often written in a neighbouring language's spelling. The canonical spelling of each, with its wrong twin, is a row in the generated language card, which also carries a worked program showing them together.
- A closure literal is
|x| { return x * 2 }. It captures implicitly, and the binding it goes into islocal, notlet. The explicit-capture spelling is the other one —fn(x) captures(read base) { ... }— and the two do not mix:|x| captures(...)is a syntax error. - A resource scope binds with
as:with File.open_read(path)? as file { ... }, neverwith file = File.open_read(path) { ... }. A resource producer is declared bodyless in an.rssi; a.rssbody cannot mint one. - An
impl P for Tblock maps existing functions into the protocol's slots (format = Point.format); it does not declare method bodies. Dynamic dispatch is a call, not a constructor:Dyn.from<Formatter, Point>(value: take point). let Some(x) = value else { return ... }binds or leaves the block, and theelseblock must diverge.Optionhas nounwrap: the fallible readers areOption.unwrap_or,Option.unwrap_or_elseandOption.ok_or.
Build a string from pieces with an interpolated string, $"... {expr} ...",
not a nested chain of String.concat(left: ..., right: ...) calls, and never
with +. Each {expr} must be a String, so convert a number inside the
braces with String.from_int(value: n); {{ and }} are literal braces.
rss fmt prints the interpolated form back as written.
| Write this | Not this |
|---|---|
$"{name} scored {String.from_int(value: score)}" |
String.concat(left: name, right: String.concat(left: " scored ", right: String.from_int(value: score))) |
readobserves an argument.mutpermits mutation and propagates writes.takeconsumes ownership.localcreates an exclusive local value.managemoves a valid local graph into managed storage.freshpromises a non-aliasing return.noescapeprevents callback escape.resourceandwithdefine scoped cleanup.- handle/weak rules govern managed references.
These constructs require no file-level enablement.
Use async fn, await, task_group, async let, select, channels,
cancellation, and abstract streams for structured concurrency. Do not generate
ambient timers, files, sockets, or subprocess access unless an explicit package
interface and binding are present in the task context.
The full right/wrong table, each row carrying the code its wrong spelling
emits, plus a worked program with a bounded channel, a select and a resource
scope, is the Structured concurrency and resources section of the generated
language card. The essentials:
- A child task is
async let handle = work(id: 1), a direct statement of atask_group.spawnis reserved and not executable (RS0015). Every named handle is consumed byawait handle?as a direct statement of the same group; an unnamedasync let _ = work(id: 1)is a background child the group drains before it returns. awaitconsumes the call, not a binding:let value = await work(id: 1)?, neverlet handle = work(id: 1)followed byawait handle(RS0022,RS0030). Anasync fnis only reached from inside a task group.- A
selectarm isbinding = await <operation> => { body };_ignores the result. The binding is not optional —await <operation> => { ... }isRS0015, as is aselectwith no arms. The arm awaits a direct async operation, never anasync lethandle (RS0015), and the losing arms are cancelled. Task.cancellation_token()returns the lexically enclosing group's token, so it is called inside thetask_groupblock and passed into the child as aread CancellationToken(RS0412). Cancellation itself is an explicitCancellationSource.- Channels:
Channel.bounded<T>(capacity: n)?, thenChannel.sender(channel: channel)andChannel.receiver(channel: mut channel)?— the receiver needsmut(RS0202).Sender.sendtakesvalue: take T, so the value must be alocalbinding created at its origin, not rebound from alet(RS0301). - Nothing
local, and no resource, may live across anawait(RS0031). Bind cancellation sources and other long-lived values withlet, and open awithresource scope only after the task group has drained. - A class instance is bound with
let, neverlocal(RS0306), and an argument aretains(...)callee stores is passedmanage value(RS0501).
Filesystem, environment, process, network, time, randomness, logging, CLI
arguments, and OS handles are not implicit core APIs. A host package declares
ordinary bodyless .rssi functions and supplies rsscript.bindings.v1 metadata.
Do not invent or automatically insert a host dependency.
Before submitting repository changes, run the local gate in docs/development/DEVELOPMENT.md. The two rules that gate matters most for generated code are:
- a program
rss checkaccepts must build, verify, and run (fixture_build_corpusbuilds every pass fixture with amain); - every diagnostic and generation change is measured with the eval corpus
(
cargo run -p rsscript-xtask -- agent-eval …) before and after.
crates/rsscript-compiler/src/interfaces.rs must contain no default host
includes; host services reach a program only through explicit .rssi
interfaces and bindings.
The generated language card and machine-readable language card, grammar, diagnostic catalog, and core-interface catalog are derived from syntax keyword tables, the diagnostic registry, and the core-interface catalog. Refresh them with cargo run -p rsscript-xtask -- language-card; verify freshness with the same command plus --check.