Error index
You saw an error; this page says what it means and where to go. Messages
are quoted the way the compiler prints them, with … standing in for the
parts that vary. Find yours with a page search.
(Organized companion: the gotchas checklist covers traps by topic rather than by message.)
This index is a curated subset, not a catalogue: the compiler has some
three hundred message forms and this page carries around a hundred. An
entry is here when all three hold — a plausible program reaches the
message; reading it alone does not settle what to do next; and no entry
already covers its family (one entry per family, its arms behind the …).
The full list, with a verdict and pins per message, is the diagnostics
ledger, and crates/vilan-cli/tests/diagnostics_ledger.rs holds this page
against it: every quoted message here must still be one the compiler
prints, and every message the ledger marks as documented must still be
quoted here.
Names and imports
“cannot find ‘…’ in this scope” · “cannot find type ‘…’”
The name isn’t visible here. Usually a missing import — though the
basics (print, Option/Some/None, Result/Ok/Err) are in the
prelude and need none. If you did import it, check for a typo or a
shadowing local.
→ Hello Vilan, spec §4.7
“… is in the prelude of the web set — set prelude = \"std::web\"”
The name (Signal, view, View) is one std’s web
prelude makes ambient, and this package is on the base one. Either set
prelude = "std::web" in vilan.toml, or import the name explicitly —
both work — the steer fires only for names the web set carries as bare
members, never for its module-carried names (style, ui), where
switching preludes would leave a value-position miss unfixed — and it
only means the manifest line is usually what
you wanted.
→ Projects, spec §4.7
“std is a namespace, not a value; import the module first …”
You wrote a qualified path like std::math::min(1, 2) inline. That
spelling isn’t supported. Import the module, then qualify through its
name: import std::math; and math::min(1, 2).
→ Hello Vilan
“… requires the … layer of std and cannot run on …”
Code reachable from this build’s entry calls into a module the platform
doesn’t have: std::fs from a browser build, std::dom from a Node
build. The error lists the call chain from main to the crossing.
Importing the module is not the problem (imports are free); reaching it
is. Move the call behind the right entry, or check the package’s
target.
→ Platforms
“… requires … and cannot run on … / reachable from …, fenced [platform(…)]”
A function declared a platform fence and something it (transitively)
reaches requires a layer one of the fenced platforms doesn’t serve. The
chain shows the path from the fence. Fences check on every compile.
Narrowing the fence, or moving the colored call out from behind it, are
the two fixes.
→ Platforms
“std::… was removed: …”
Sixteen aliases lived at the std root for one release — std::print,
std::panic, std::Default, the primitives — and the prelude serves the
same purpose better, so they were deleted. The message names the way
forward for the one you wrote: a primitive (str, i32, …) is always in
scope and needs no import at all, print is in the default prelude, and
anything else has its real module path (std::io::panic,
std::default::Default).
→ Hello Vilan, spec §4.7
“… is a reserved package name: …”
std, pkg, macro_std and vilan each already mean something as an
import root, so a [package] name or a dependency key cannot claim one —
a dependency named std used to REPLACE the standard library silently.
Rename the package; or, for a dependency, rename the key, which is only
the name you import it by and is free to differ from the library’s own.
→ Projects
“cannot find module ‘…’ to import”
The path names a module file that doesn’t exist. pkg::routes means
“routes.vl in this package’s source root”. Check the file name and
the package you’re in.
→ Hello Vilan
“module ‘…’ resolved to ‘…’ on disk, but it is imported as ‘…’” Your filesystem ignores case (NTFS, and macOS by default) and answered the import with a differently-cased file. Module names match byte for byte (§4.2), so this would fail to build on a case-sensitive filesystem. Rename the file or the import so the two agree. → Names, modules, and packages
Types and generics
“Expected …, but got … instead.”
The general type mismatch. One special case surprises people: an i53
mixed with a bare integer literal: the literal is i32, and there are
no implicit conversions. Suffix it (stamp + 1000i53).
→ Values and types
“generic parameter ‘…’ is missing the bound ‘: …’ required by this call”
You called something that needs a capability (say PartialEq) with a
generic parameter that doesn’t declare it. Add the bound to your
signature: fun caller<U: PartialEq>(…).
→ Data and traits
“cannot call method ‘…’ on …”
The value’s type doesn’t have that method. If the type is a generic
parameter, you probably need a bound. If it says something like
|i32| i32, you’re calling a method on a closure, often a sign a
different value was passed than you think.
→ Data and traits
“‘…’ does not implement trait ‘…’: missing ‘…’”
An impl … with Trait doesn’t provide every required method, or a bound
demands a trait the type never implemented.
→ Data and traits
“‘…’ is a trait, not a type: a trait is not a value type (vilan has no trait objects)”
A trait’s name was written where a type belongs — a parameter, a return
type, a struct field, or a generic argument like List<Display>. Traits
are bounds, not types, so no value can ever have that type: the impl
is fine, the signature is not. Write the generic the message spells out —
fun show<T: A>(v: T) — or, inside the trait’s own declaration, write
Self, which is what a trait naming itself in a return position always
meant. The note points at the trait, which may live in another module.
For “one of several things at runtime”, use an enum. A let binding’s
own annotation is not this error: there a trait is a constraint on the
inferred type, see the next entry.
→ Data and traits
“‘…’ does not implement trait ‘…’, required by the annotation on ‘…’”
A let binding’s annotation named a trait, which reads as a constraint
on the value’s own type — the binding still has the concrete type its
initializer produced — and that type has no impl of the trait. Either
implement it, or annotate with the type you meant. Note that two if
arms of different types fail earlier, at the arms: the annotation is not
a widening, so there is nothing for two types to meet in even when both
implement the trait.
→ Data and traits
“‘…::…’ has no default body, so ‘…::…(..)’ has nothing to call”
An associated function (a trait fun with no self) was called on the
trait, but the trait only declares it — each impl supplies its own body,
and with no receiver there is nothing to pick between them. Call it
through an implementing type, Type::func(..), or give the trait’s
declaration a default body, which is what makes the trait’s own spelling
callable.
→ Data and traits
“cannot call ‘…’ on a value of bare trait type ‘…’”
The same rule reached from the other side: a receiver whose type is a
bare trait has no concrete implementation to dispatch to. Reachable
inside an impl whose subject is itself a trait, where self is
abstract; elsewhere the annotation that produced the value is refused
first.
→ Data and traits
“‘…’ is already defined for ‘…’; remove or rename this one” Two impls declare the same name for the same type, and neither name belongs to a trait. Nothing ranks them, so one of the two would simply never run — the note points at the other declaration. Delete the copy you don’t want, or rename it. It is reported where it is defined, not where it is called.
A type has one namespace, so receiver position is not part of the
name: a static fun new() and a method fun new(self) for the same
type collide with each other too. Give one of them a different name.
→ Names, modules, and packages
“‘…’ is already implemented for ‘…’; remove or merge this impl”
The same trait is implemented twice for the same type. A trait has one
implementation per type, so the second block would simply never run —
neither at value.method() nor through a T: Trait bound. Merge the two
bodies into one impl, or delete the one you don’t want; the note points
at the first, and names its module when it lives in another file.
Only an exact repeat is refused. A parameterized trait may be
implemented once per set of arguments — impl Bag with Into<Cup> and
impl Bag with Into<Mug> are two implementations, not one written twice
— and an argument you leave to a = Self default counts as the one it
defaults to, so with Combine and with Combine<Bag> are the same
implementation of Combine for Bag.
→ Data and traits
“‘…’ is ambiguous on ‘…’: both ‘…’ and ‘…’ provide it; call ‘…’ to pick one”
Two traits supply the same method name for this receiver (or, for a
generic receiver, two arms of its T: A + B bound), and the type has no
inherent method of its own to outrank them. Say which one you mean with
Trait::method(receiver, …) — the message spells both options out with
your own receiver already substituted in.
→ Names, modules, and packages
“next is ambiguous on ‘…’: both ‘…’ and ‘…’ provide it…, and a for loop has no spelling that names one”
The loop’s counterpart to the message above, for the iterator protocol
(next, or next_mut for for x in &mut subject). Two traits provide
the member — declaring it, or supplying it as an inherited default — and
no inherent member outranks them. A call can pick a provider with
Trait::next(receiver); a for has no such spelling, so the fix is the
one the message names: declare next on the type itself, where it beats
every trait-provided one.
→ Collections
“‘…’ is not an inherent member of ‘…’: … provide… it; call … instead”
Type::method(receiver) means the type’s own method. This one comes
from a trait, so name the trait at the path head instead:
Trait::method(receiver).
→ Names, modules, and packages
“‘…’ does not implement ‘…’, so ‘…::…’ cannot be called on it”
A Trait::method(receiver) call named a trait the receiver’s type has no
impl … with for. Implement the trait, or call the method the receiver
does have.
→ Data and traits
“… match the receiver convention” · “… match the parameter convention” · “… match the declared type” · “… match the declared return type” · “… match the declared parameter list” · “… match the trait’s type-parameter list”
A method an impl … with Trait provides must match the trait’s
declaration, not just its name: the receiver convention (self / &self
/ &mut self / own self), the parameter count and each parameter’s
convention and type, the return type, and, for a generic method, the
type-parameter count. Types are compared with Self read as the impl’s
subject and the trait’s generic parameters read as the with-clause
arguments (impl Meters with From<Feet> expects fun from(value: Feet): Meters). When the trait’s parameter has a Self default and you supply
no argument, it reads as the subject: impl Meters with Add expects
fun add(self, b: Meters): Meters, while impl Meters with Add<Feet>
expects fun add(self, b: Feet): Meters — the argument changes, the
Self return does not. A generic method’s own type parameters are held
to the trait’s promise too: declaring fun go<T>(&self, x: T) and then
implementing fun go<T>(&self, x: str) narrows what the trait promised
to accept, and is rejected. Asyncness is not required to match: an async
impl of a synchronous trait method is allowed (dispatch is monomorphized,
so callers await it regardless).
→ Data and traits
“match is not exhaustive: missing …” · “match is not exhaustive: add a catch-all _ leg”
Some values have no arm. Handle them or add _ => …. This error is
the feature: it’s what fires everywhere when you add a variant. A
guarded leg does not count towards it — a guard tests the value, and
the check reasons about the type — so B if ready => … leaves B
missing, and a note points at the guard to say so. That also means the
last leg may not be guarded: give it a _ => … after it, so the value
the guard rejects has somewhere to go.
The hole may be below the top level, and then the message names one
uncovered value as a pattern that would cover it —
“missing Pair::Of(Align::End)”, “missing Wrapped::Of(_)”,
“missing (Align::End, Align::Start)”. Coverage is judged over the
whole pattern tree: a payload or tuple element tested with a literal
proves nothing about the values it does not equal, and only a binder or
_ covers an unbounded one. Where the hole is the subject’s whole
domain the message asks for a catch-all instead, since naming a value
there would say nothing the _ does not.
→ Control flow
“struct ‘…’ has no field ‘…’” · “variant ‘…’ does not belong to the matched enum”
A field or variant name is off. For the variant case inside match,
patterns bind with let: a bare misspelled variant is an error here,
never a silent catch-all. When a real field is a close-enough edit away
the field case adds a note — “did you mean entries?” — and the editor
turns it into a “Change to entries” quickfix that rewrites the name.
Close enough is a real threshold: "entires" suggests "entries", and
"x" suggests nothing at all.
→ Control flow
“… expects N arguments, but got M instead: … is missing.” ·
“… expects N fields, but got M instead: … is not a field of ….”
A call or a struct literal is short of, or over, its declared list. The
message names the callee or struct, not just the counts — two calls
on one line no longer leave you working out which — and, when it is
short, the specific parameter or field that is missing (arguments bind
positionally, so which one is absent is unambiguous; too many gets no
such guess, since which extra to drop is not). A secondary note points at
the subject’s own declaration: “distance is declared here”.
→ Functions & closures, Data and traits
“Expected …, but got void instead: an if with no else produces void.” ·
“…: the ; discards this body’s last value.” ·
“…: this body ends without producing a value.”
Three shapes of “nothing came back”, each naming its own cause instead of
reporting a bare void. The first is an if/else if chain in tail
position with no final else — add the branch. The second and third
anchor on the callable’s closing brace, one character wide: the fix
is almost always the trailing ; that discarded the value, and the
editor offers “Remove ;” on it. A closure’s own : T return annotation
is checked against its body directly, so these reach a closure literal as
readily as a fun. Passing a wrong-typed value is a different error and
still points at the value.
→ Functions & closures
“… compares two values of the same type, but the operands are … and …”
Comparisons follow the trait model (== is PartialEq, < is
PartialOrd): the right operand must be the left’s type, and there are
no implicit conversions. An unsuffixed literal adapts to its peer
(stamp < 3 is fine for an i53 stamp); two differently-typed
variables need a suffix or an as_* conversion. Related:
“bool has no ordering” (compare with ==/!=) and
“&& takes bool operands” (Vilan has no truthiness).
→ Values and types
“… takes two values of the same type, but the operands are … and …:
… is wider than what … admits”
The same rule reaching a generic parameter on the right of a native
operator — total - value, total & value, total < value,
total == value where value: T. An operator belongs to its LEFT
operand: the right one has to be a member of what the left admits, and
a bound can prove membership only where that set has a trait naming it.
i32’s does not — i32 compares against i32 and nothing else, and a
bound promises a trait’s methods, never that the parameter is an
i32 — so no bound rescues this, and adding one is not the fix.
Convert where the type is known and declare the operand i32. A native
left operand never dispatches either: an impl i32 with Add is not
consulted, because the host operator is the semantics there.
→ Values and types,
Data and traits
“+ on str concatenates, and … has no string form: concatenating it
renders the value’s runtime shape …”
A value with no string form was concatenated into one. Only str, the
numbers and bool render themselves; a struct lowers to a tuple, an
enum to a tagged array and a List to an array, so the host would have
printed 1,2 for a Point { x = 1, y = 2 }. Render it first —
point.to_string(), adding an impl Point with Display if the type has
none. An interpolated string is this same concatenation (i"a{x}b"
is ("" + "a" + x + "b")), so a hole gets the identical error and the
identical fix; the same goes for a css block value that mixes text
with holes. A backed enum is included in the refusal on purpose: its
backing is a lowering detail, not a rendering the program chose.
A generic parameter gets the same error worded for its bounds — an
unbounded one promises nothing, and one bounded to something other than
a string form (T: Add) promises the wrong thing. Bound it with
Display and the bare operand concatenates: the implementation is
called at each instantiation, so "v=" + value and i"v={value}" both
render the value rather than its runtime shape.
→ Values and types, Strings
“+ on … adds, and str is not a number: only a str LEFT operand
concatenates …”
The concatenation is the right way round only when the string is on the
left, because the expression takes its type from its left operand:
count + "!" would have typed as i32 while producing a string. Write
"!" + count, or convert with count.to_string() + "!".
→ Values and types
“+ adds two values of the same type, but the operands are … and …”
The ==/< rule above, for addition: no implicit conversions between
numeric types. An unsuffixed literal still adapts to its peer
(stamp + 1000 is fine for an i53 stamp); two differently-typed
variables need a suffix or an as_* conversion (ratio + count.as_f64()).
A generic parameter on the right of a number’s + is refused for a
reason of its own — “T is wider than what i32’s add accepts” —
and, unlike the concatenation above, no bound fixes it: str’s
admitted set has a trait that names it (Display), a number’s has none,
so T: Add promises T + T and says nothing about i32. Convert where
the type is known and declare the operand i32.
→ Values and types
“+ adds numbers and concatenates str, and … is neither: it has no
Add …”
bool and backed enums are native for == and < without being
numbers, so + on one would have added its lowering: true + true is
2, typed as a bool, and two backings sum to something that is rarely
a variant. Match on the variant, or hold the number you mean.
→ Values and types
“type ‘…’ does not implement the … operator; add impl … with … providing …”
An operator was used on a type without the matching trait impl: +
needs Add, == needs PartialEq, </<=/>/>= need
PartialOrd (implement partial_compare once; the operators dispatch
through it, and lt/le/gt/ge come free as defaults).
→ Data and traits
“the literal … is out of range for … (…)”
The number doesn’t fit the type. For i53/u53 the range is ±2^53,
JavaScript’s exact-integer window. Bigger integers take BigInt (7n).
→ Values and types
“unknown numeric suffix …”
The letters after the number aren’t a type. If it says i64 or u64:
those were renamed to i53/u53.
→ Values and types
“substring start … is negative”, “substring end … is negative”,
“substring end … is before its start …”, “substring end … is past the
length … of this string” (each continuing “— the range must satisfy
0 <= start <= end <= len, and substring never clamps or swaps”)
substring(start, end) was written with literal bounds outside its rule, so
it is refused here rather than at run time. The host’s own substring would
have corrected the call — clamping a negative to 0, swapping an inverted
pair — and returned a string that is not the one asked for; s.substring(k, -1)
in JavaScript is s[0..k], the prefix, not the suffix. Write s.len() for
“to the end”, and reach for strip_prefix/strip_suffix to drop a known
affix. Non-literal bounds are checked at run time (below).
→ Strings
“type of … could not be resolved” Inference gave up somewhere upstream. This error is usually the echo of another one, so fix the first error in the list. When it appears alone, an annotation at the binding usually grounds it. → gotchas
“… have mismatched types: expected …, but got … instead.”
Every leg of a match, and both arms of a value if, produce the one
value the construct has, so they have to agree on a type. The refusal is
anchored at the arm that disagrees rather than at the whole construct, and
it names which construct it is (“match legs”, “if arms”). An arm that
always leaves — a ret, a panic — contributes nothing to the merge and is
never the one blamed.
→ Control flow
“‘…’ is ambiguous on ‘…’: both ‘…’ and ‘…’ provide it and neither impl subject is more specific than the other …”
Two impl blocks match this receiver and neither is narrower than the
other, so no spelling at the call site picks one — both are the same trait
at the same instantiation. Vilan resolves overlap by specificity (a
constructor-headed impl outranks a blanket impl type T), so the fix is
at the definitions: narrow one subject until it is the more specific of
the two.
→ Data and traits
Memory and mutation
“cannot mutate immutable ‘…’”
The binding was declared with let. Declare it mut, or take
&mut self if you’re inside a method.
→ The memory model
“a view cannot escape its scope: it may not be returned, stored in a field, placed in a collection, or carried in an enum payload. …”
Views (&x, &mut x) are short-lived by design: lend, use, done. To
keep a reference around, store a plain value, a Handle into an
Arena, or a Shared cell.
→ The memory model
“cannot reassign ‘…’ while a view into it is live (rule 4 …)”
Replacing the whole value would detach the view from live storage. Finish
using the view first (its life ends with its block), or re-derive it after
the replacement. Views anchor wherever they come from: &x, a
view-returning call (list.at(0), arena.get(h)), or a Some(let v)
capture of one. The rule is the same for all three.
→ The memory model
“cannot mutate ‘…’ with ‘.…(..)’ while a view into it is live (rule 4 …)”
The call may advance the container’s geometry (grow, shrink, reallocate,
swap an aggregate field) while a view points into it. Only
geometry-advancing callees trigger this: a method that writes fields or
elements through &mut self passes freely (the compiler infers which is
which). Do the mutation before taking the view, or after its block ends.
→ The memory model
“cannot hold a view across …: ‘…’ is still live here. …”
Your function suspends while a view is live, and whatever it points into
could change during the pause. Re-derive the view after the suspension
(rows[i].field again) instead of keeping it. The message names await,
but the question is whether the call can suspend: calling an async
function without the keyword is the sanctioned spelling and suspends
identically, so this fires on a line with no await on it — including
through a sync-looking function that reaches something async.
→ The memory model, Async
“view binding ‘…’ cannot be mut: a view cannot be rebound. …”
mut v = &mut x doesn’t mean what it would in Rust. Declare the view
with let; assigning through it (v = …) already writes the target.
→ The memory model
“a closure cannot capture the view ‘…’: a view is second-class and the closure may outlive the place it views. …”
A closure body named a view binding (let v = &mut x, a for e in &mut list item, or the result of a view-returning call) declared outside it.
A closure captures the binding, and nothing says when the closure runs,
so the capture would outlive the place. The two fixes are the two ways it
stops being a capture: read the value out first (let n = *v;, then
capture n), or take the view as a parameter of the closure
(|v: &mut i32| *v), which is a per-call loan. A &/&mut parameter of
the enclosing function may be named inside a closure — it views the
caller’s place — but that closure may not then escape. An async closure
gets the sharper message below instead.
→ Functions and closures, spec §6.9
“an async closure cannot capture the view ‘…’: the capture would be held across the closure’s suspension points. …”
The rule above, at a closure that suspends: on top of outliving the
place, the capture would be live across an await, where any turn may
invalidate it. Re-acquire the view inside the closure after the
suspension, or pass a value or a Shared/Handle in.
→ The memory model, Async
Resources
A resource type has a single owner and moves rather than copies; a
struct, enum, or tuple holding one is a resource too, inferred by
containment (Option<Database> is a resource, Option<i32> is not). A
resource moves on binding (let b = a), on own-passing, on return, and
into a constructor; it is loaned (no ownership change) through self,
&, and &mut. The Drop destructor trait and its restrictions are below.
After a resource’s last use the compiler runs its destructor; resources
whose last use is the same statement discharge in reverse declaration
order. Destruction goes through try/finally, so ret, jump, and a
thrown panic all run it on the way out; a resource without a Drop impl
still has its fields destroyed. A resource built inside one expression and
never bound is owned by its statement and destroyed at that statement’s
end. A module-level resource lives for the process and never drops. A drop that panics while a panic is already
unwinding replaces the in-flight error (JS finally semantics). The tutorial
is Resources; the normative rules are spec
§6.8.
“use of … after it was moved: a resource has a single owner”
The binding was moved (bound to another name, passed to an own
parameter, returned, or matched by value) and then used again. The note
points at the move. Loan it instead (&x / &mut x, or a method call),
or, if you really need two owners, restructure with Option + take.
→ Resources
“cannot move a resource field out of a live aggregate: … no partial moves …”
let x = s.db, or passing / returning s.db by value, would move a
resource out of a struct that is still alive: there are no partial moves.
Loan the field (&s.db, &mut s.db, s.db.method(…)), or make the field
an Option<…> and take() it out.
→ Resources
“… is moved on one path through this branch but not another: …”
An if/match moves the binding on some paths and not others, so its
end-of-scope ownership isn’t static (there are no runtime drop flags). Move it
on every path, on none, or hold it in an Option and take() on the
path that consumes it. A diverging leg (one that rets or jumps out) is
exempt: it never reaches the merge.
→ Resources
“… is declared outside this loop and moved inside it: …”
Moving a binding from a loop body would move it again on the next
iteration. Move a value declared inside the loop, or loan the outer one
(&x / &mut x).
→ Resources
“… is a module-level resource: it has process lifetime and cannot be moved …”
A top-level let resource lives for the whole process and never drops (the
serve-forever server’s Database). Consuming it (moving it into a local,
passing it to an own parameter, or drop(x)) would hand a
process-lifetime resource to a droppable owner and close the shared handle
out from under the rest of the program. Reach it by loan only: method calls,
&x, &mut x. To own a database that closes after its last use, open it
in a local instead.
→ Resources
“a closure cannot capture the resource …; …”
A closure or async/spawn body referenced a local or parameter resource
from an enclosing scope; capturing it would give the closure a second owner.
Pass a loan into the call, give ownership to the struct that owns the
closure’s lifetime, or hoist the resource to module level: a module
global is loan-only and process-lifetime, so a closure may reference it
without becoming an owner. (A closure’s own parameter is per-call, not a
capture; injected context-clause bodies are unaffected.)
→ Resources
“… is not move-clean when instantiated with a resource: …”
A generic function or method was called with a resource type argument
(Option<Database>, wrap(db)), and its body (checked with that type
parameter treated as a resource) breaks the affine rules in one of three
ways. It uses a value of the parameter’s type more than once (moves it
on some paths but not all, or captures it in a closure); a resource has a
single owner. Or an own parameter of resource type is never moved
out: because the generic body is shared across every instantiation, it
cannot run a destructor, so an own T must be moved out on every path
(returned, or handed to another owner), or the function must take a
concrete type. Or it passes such a value to drop<T>: that erased
body has no concrete destructor either, so the resource would leak
(drop(x) on data is a fine no-op, which is why the data instantiation
stays accepted; destroy at a concrete type, or move the value out to the
caller). The error is spanned at the call (the instantiation), with a
note into the generic’s body. A
clean generic moves each such value exactly once (as Option::unwrap(self): T does), never copying, capturing, or forwarding it to the sink;
drop(concrete) on a concrete resource is the destructor. Instantiating
the same generic at a data type is unaffected.
→ Resources
“the resource … cannot be used where any is expected: …”
any is a data sink, and a resource must keep its single owner: passing
one to print, binding it to let x: any, or returning it as any
would launder the discipline away. Debug-print the resource’s fields
instead.
→ Resources
“… cannot hold the resource ……: … a native container’s internals are host code …”
List, Map, Set, and the external generics (Shared, Task,
Promise, Context) can’t hold a resource: the move checker
can’t see inside host storage. Option is the sanctioned resource
container; or keep the resource in a struct field.
→ Resources
“field … of [derive(Wire)] / [derive(Json)] / [derive(Hashable)] / [derive(PartialEq)] type … is the resource …: …”
A resource is not plain data: it cannot be serialized, hashed by
value, or compared by copy. Drop it from the derived type, or carry a
plain-data handle (an id, a key) in its place. The check reaches a field
nested two structs deep and an enum variant’s payload, not just a direct
field.
→ Resources
“… cannot be derived for the resource … …: …”
The same rule with the resource in the other position — the derived type
is the resource. Serializing it copies a handle out of its owner, and the
reading half is worse: Wire’s rebuild and Json’s from_json build a
value out of bytes, which for a resource is a second handle nothing owns
and nothing will close. Send a plain-data name for the resource instead
(an id, an Arena handle) and keep the resource on the side that owns it.
The other derives are unaffected: PartialEq and Debug read a resource’s
fields through the loan and stay available.
→ Resources, Services
“… implements Drop but is not a resource: … declare it a resource …”
Drop (the destruction hook) may be implemented only for a resource
type. A destructor without move discipline is the double-close bug:
copy the value and each copy would run drop. Declare the type resource
so it moves instead of being copied. (Plain-data, framework-driven teardown
uses the cooperative Disposable protocol, not Drop.)
→ Resources
“drop for … is async: teardown must be synchronous …”
A drop body may not be async, nor await (call an async function): a
destructor runs synchronously. Cancel owned tasks through an
OwnedNursery (whose own drop cancels them) rather than awaiting them.
Awaited teardown is a future design.
→ Resources
“drop for … requires an ambient context: teardown must be context-free …”
A drop body reached something that needs an ambient context, most often a
Signal write, which threads the current turn as a hidden argument. A
destructor’s call sites are scope exits, which thread no context, so it
cannot receive one. Keep teardown context-free: hand turn-joining or
signal-writing work to an owner that runs inside a turn.
→ Resources
Async
“… receives an async closure, but its type awaits nothing; declare it async || T (or return void for spawn semantics)”
A closure that suspends was stored into a struct field typed as a
plain, value-returning closure (at the literal or a later assignment).
Either the field should be async |…| T, or, if fire-and-forget is
fine, its return type should be void. (A plain parameter no longer
produces this error. It adapts: the callee instantiates an async copy
that awaits the callback.)
→ Async, Functions & closures
“… requires a synchronous closure (sync): its completion is part of the declaring function’s synchronous protocol …”
The parameter is a sync contract position (Signal::map,
set_with, turn/batch bodies, the UI render callbacks) where the
callback must finish inside a synchronous protocol, so it cannot adapt.
Move the async work outside the callback: an explicit turn(…) whose
awaiting body holds one turn across its awaits, Draft/optimistic
for local-first commits, or a spawned async { … } block. The
transitive form (“this call passes an async closure that reaches …”)
points at the call that made the closure async and notes where it was
forwarded.
→ Async, Reactivity
“… is a host (external) function: it cannot await a Vilan closure …”
Host code can’t await your callback, so an external function’s
value-returning closure parameters only accept synchronous closures
(void-returning ones spawn, as everywhere). A parameter declared
async |…| T is exempt: that is the host’s explicit contract to await
the closure itself.
→ Async
“an async closure cannot adapt a trait/generic-dispatched call …”
Adaptation instantiates a statically-known callee, and a
trait/generic-dispatched call doesn’t have one: the concrete method
varies per instantiation. Bind the receiver concretely before the call,
or declare the trait method’s parameter async || T so every impl
takes the typed channel.
→ Async
“… returns an async closure, but its declared return type awaits nothing; declare it async || T (or return void for spawn semantics)”
The function’s declared return type is a plain, value-returning closure,
but a ret (or the tail) hands back a closure that suspends. Mark the
return type async || T so calls through the returned value await
(make()() and let go = make(); go() both do), or return a
void-returning closure for spawn semantics.
→ Async
“the initializer of … calls …, which is async: a module-level binding cannot await”
A top-level let runs when the module loads, and module initialization
is synchronous: there is no enclosing function to become async, so the
value would be a live promise wearing the wrong type. Wrap the work in
a function and call it from main. The variant “the initializer of
… runs a closure that awaits” is the same rule when the awaiting
thing has no name: an adopted async closure applied directly, a
run(value, body) whose body suspends, or a nursery at top level.
(Creating an async closure at top level is fine; it awaits nothing
until called.)
→ Async
“the initializer of … awaits: a module-level binding cannot suspend”
The same rule, reported at an explicit await whose operand is not an
async call — a Task-valued binding, a spawn (await async f()), or a
Task returned by a plain function. Module initialization is
synchronous by design, so every one of these is refused wherever the
await sits in the initializer’s expression. The note names the fix:
spawn at module level (let pending: Task<T> = async work();), which
starts the work at load without suspending, and await the Task in
main. An await inside a closure the initializer merely creates is
not the initializer’s own and stays legal.
→ Async
“… form an initialization cycle: module-level bindings initialize in dependency order, and a cycle has no such order”
Module-level bindings initialize in dependency order (spec §7.1): each
one runs after everything its initializer evaluates at load: the
bindings it reads, plus whatever is read inside anything it calls on
the way. A cycle among those evaluations has no valid order, so it is
refused at compile time; the message names the round trip (via A → B → A) and each participant’s declaration. The self-referential form
(“…’s initializer evaluates … itself, which has not initialized
yet”) is the one-binding case of the same rule. Creating a closure
evaluates nothing, so two module-level closures may name each other
freely; moving one of the cycle’s reads inside a closure is the usual
fix. If the chain runs through a dispatched call, every implementation
of that method participates, including one your program never
instantiates; the message says so when it applies.
→ Execution
“! requires the nearest enclosing function to declare an Option/Result-compatible return type …”
! propagates the failure by returning it, so the surrounding
function must return an Option/Result that can carry it. Inside a
closure or a UI handler, match instead.
→ Control flow
Contexts and UI
“context owner_scope is read here, but this code can be reached without an enclosing run”
The most common first UI error: you built reactive state (an effect, a
binding) outside every ownership boundary. Wrap the entry point in
mount_root, or run_with_owner in a test. The error points at your
effect/map/or call; the note under it shows the read inside the
library — the standard library, or an external dependency package your
code calls — that your call reaches, and every call of yours on the
uncovered path above it is underlined too (“the context requirement
flows through this call”) — follow the chain up to where the ownership
boundary belongs. Calls inside a covering run are clean and never
appear in the chain, and an external package’s internal calls are never
underlined: the error always lands on code you wrote. (Your own
workspace is yours: a member package your project root’s packages
declares reports exactly like your entry’s modules — the read anchors
at itself, in the member’s file.)
→ Building UI, Reactive state
“… reads context …, so it can’t be used as a value”
A function that reads an ambient context (like the current owner) can’t
be passed around as a plain closure: the context channel would be
severed. Wrap it in a closure literal at the use site instead.
→ Functions & closures
“an injected (context-typed) closure can only be called, forwarded …, or passed to run”
Injected closures (the ones with context clauses in their type) are
deliberately restricted so the ambient value can always be threaded to
them. Don’t store them; call or forward them.
→ Functions & closures
“unused result of a [must_use] call: bind it (e.g. owner.take(…)), or let _ = … to discard.”
The call returns something that stops working if you drop it (a
Subscription, typically). Keep it, hand it to an owner, or discard it
on purpose with let _ = ….
→ Reactive state
“… is deprecated; use …” (a warning)
The named function still works — this never fails a build — but it is
marked [deprecated] and scheduled for removal, no earlier than the
minor release after this warning first shipped. The message names the
replacement; switch to it at each warned use site. The removal itself,
when it comes, is announced under the CHANGELOG’s Breaking entries with
migration notes.
→ spec §3.3 for the attribute
Wire and rpc
“field … of [derive(Wire)] type … is …, which is not Wire: …”
Something unserializable (a closure, a Signal) is inside a payload
type. Wire types carry data only: scalars, str, bool,
List/Option of Wire, and other Wire types.
→ Services & RPC
RpcError::Contract at connect time
Client and server were built from different versions of the service.
Rebuild both. During development, a leaked old server still holding
the port is the usual culprit: ss -tlnp | grep <port> and kill it.
→ Services & RPC, gotchas
RpcError::Transport("not connected") / ("connection lost")
The connection is down (fail-fast) or dropped mid-call (in-flight
rejection). Nothing is retried automatically, because your rpc might
not be safe to repeat. Retry at the app level if that’s correct; a
draft’s next push already does.
→ Services & RPC
Compile-time evaluation
“asset::emit outside a const expression”
Styles (and other build assets) are constructed at compile time. Build
the Style in a const (let card = const style()…); select and merge
already-built styles at runtime. The channel’s other directions —
asset::emit_keyed and asset::read — are compile-time-only the same
way.
→ Styling, Macros & const
“asset::emit_keyed cannot order the css kind: the style sidecar is
ordered by the CSS cascade, not by a contribution’s key”
The stylesheet’s order is decided by the cascade — base rules before
@media blocks, media blocks by ascending min-width — so a sort key
handed to it would have nowhere to apply. Write the rule with
asset::emit("css", …), or let std::style own the
sheet. emit_keyed is for a kind of the program’s own.
“… is compile-time-only; evaluate this call inside a const expression”
The same rule, caught statically: some function on this call path reaches
asset::emit, asset::emit_keyed or asset::read, and the call itself
sits in runtime code.
The span is the outermost runtime crossing — the call that leaves ordinary
code and enters compile-time territory — so wrap that call in a const.
A crossing through trait dispatch counts too: a generic call is charged at
the entry whose concrete type selects an emitting impl (a clean impl of the
same trait member through the same generic stays legal), and a dispatch the
compiler cannot resolve — a shared default body’s self call — is refused
for every receiver, conservatively, since letting one through would compile
clean and throw at run time.
“cannot read … (resolved against the package root to …): …”
A const asset::read(path) found no readable file. Paths are relative
to the package root — the directory imports resolve under, never the
directory the compiler happens to run from — and the message shows where
the resolution landed. An absolute path, or one that escapes the package
root (../…), is refused before any read: the file channel reads the
project, so the build can track every input it depends on. The refusal
is on the path as written — a symlink inside the package is ordinary
layout and is followed, so a name that resolves elsewhere is not an
escape (Const evaluation).
“… is compile-time-only; call it directly inside a const expression — a
compile-time-only function has no runtime value form”
A compile-time-only function (or a closure that reaches one) was used as a
value — passed to a higher-order function, stored in a binding, built as a
closure literal — rather than called. The compiler cannot follow a call made
through a value, so it refuses the value instead. Call it directly inside the
const: const apply(styled) is fine, apply(styled) at runtime is not.
“a const result must be plain data; this evaluates to …”
The const expression produced something that can’t be baked into the
output (a closure, a host object). Fold values, not behavior.
→ Macros & const
“const evaluation failed in f: …”
The computation ran and something inside it went wrong — a panic, a
subscript past the end. The squiggle is on the const expression,
because that is the expression the compiler is refusing to fold; the
message names the function it failed in, and the note points at that
function’s declaration with the call chain that reached it. (The
compiler cannot point inside the callee: the tree it evaluates is the
compiled output, which carries no source positions.)
“const evaluation did not finish within the compile-time budget in
f: …”
The same thing, but the computation never finished rather than failing:
it exhausted the interpreter’s step budget (an unbounded for) or its
call-depth cap (unbounded recursion). The build fails rather than
hangs. Fix the termination condition, or move the work to runtime.
Syntax
A syntax error no longer blanks out the rest of the file. The parser
recovers at statement and item boundaries — a statement it cannot read is
reported and skipped to the next ;, }, or declaration keyword — so
the statements around it, the functions below it and the whole file tail
still reach the type checker, and the diagnostics they already had stay
where they were. vilan check type-checks that salvaged file too;
vilan build still stops, because a recovered file is not something to
emit from.
“expected ; to end this statement”
A statement ran into the next one. The message is anchored at the gap
where the ; goes — the last character before it, not the head of the
statement below — and the editor offers an “Insert ;” quickfix there.
It answers a missing ; after an import or a use as well.
→ spec §3.2
“unclosed (: expected a matching )”
A delimiter you opened and have not closed yet — the defining shape of
code mid-edit. It reports on the delimiter you typed, not on whatever
the parser tripped over several lines down. A closing delimiter that is
wrong inside a finished list keeps its own, more precise message
(found ';' expected ',' or ')', on the exact character where the list
broke).
→ spec §3
A struct literal in a condition parses as the block
Struct literals are ordinary operator operands (Point { … } == q
compares), but condition positions exclude them: after if Foo or a
match subject, the { is the block/arms, by design. Written without
parentheses, if p == Point { … } { … } leaves a bare Point as the
condition’s operand, which reports “Point is a type, not a value”.
Parenthesize the literal: if p == (Point { x = 1 }) { … }.
→ spec §3.8
“# is not a vilan token …” · “@ is not a vilan token …”
Both turn up almost only inside a css block. A colour is written as a
hole that routes through the Color type — color: {Color::hex("#333")};
— which is what lets the type carry its own :root line. And a css
block has no at-rules of any kind: a media query is spelled as a
breakpoint combinator (.md { … }), and a declaration block under a
selector of your own is std::style::declare.
→ Styling
“pub is not a vilan keyword …”
pub (and public) is an ordinary identifier here, so pub fun helper()
reads as the expression statement pub followed by an item — which used
to report a missing ; three columns in, a true statement about a
program nobody wrote. Vilan has no visibility marker to reach for: a
module’s items are importable as written, so the fix is to delete the
word. export is a different thing — it re-exports something this
module imported (export import pkg::io::panic;), so importers of this
module see the name as if it were declared here.
→ spec §4.3
“a mutable binding is spelled mut x = … …”
let mut x = 1 is the Rust spelling. let and mut are vilan’s two
binding forms, not a keyword and a modifier on it: let binds
immutably, mut binds mutably, and writing both is neither. The old
message (“found ‘let’ expected a statement”) named the one token that was
right.
→ Values and types, spec §3.3
“a { inside an i"…" string opens an interpolation hole …”
A trailing note on whatever the parser found inside the hole. { is the
hole opener, so a literal brace has to be escaped: write \{ and \}.
Without it, i"body { color: red }" reports a failure about an
expression the author never wrote — and code that generates braces (a CSS
rule, a JS body, a JSON object) hits this on nearly every line.
→ Values and types
“a string cannot span lines unless it is triple-quoted …”
A "…" or i"…" ran into a line break before its closing quote. Either
the quote is missing (the common case; the error is reported on the
string’s own line rather than wherever the next " happens to be), or
the text really is multi-line, in which case write it """…"""
(i"""…""" with holes). A single line break inside a one-line string is
\n. Nothing escapes a line break: a trailing \ does not continue the
literal onto the next line.
→ Values and types, spec §2.3
“Name is a type, not a value” (also “a trait / a type parameter /
a module, not a value”)
A type, trait, type parameter, or module name was used where a value is
expected (let q = Point;). A type names a kind, not a runtime value:
construct it (Point { … }), name a variant (Color::Red), or call a
static (Point::new(…)).
→ spec §4.2
Panics
These are not compile errors — they are what the program prints when it stops at run time. Each exists because the alternative was a host-level message naming nothing you wrote — or, worse, no message at all and a quietly wrong answer.
“… : … is not one of its values” (e.g. Align: "middle" is not one of its values)
An exhaustive match over a backed enum met a value outside its
variant set. A backed enum lowers to a bare host string or number, so its
runtime domain is the host’s, and exhaustiveness is a proof about the
variant set rather than about the value: an external fun return, a
host callback’s argument, or a decoded payload can carry anything. The
last arm is tested like every other one and the else traps rather than
answering with whichever variant happened to be last. A match with a
_ arm is unaffected — the out-of-set value takes the arm you wrote — and
Enum::parse(text) is the shape to reach for where an unrecognized value
is one of the answers you expect: it returns Option.
→ Data and traits, spec §5.2
“substring out of range: the length is … but the range is …..…”
substring(start, end) was called with bounds outside 0 <= start <= end <= len, computed rather than literal (a literal pair is refused at compile time
instead). The rule is absolute: no clamping, no swapping, and an end past the
length is an error rather than a truncation. This one is a panic because the
host would not have raised anything — JavaScript’s substring clamps a
negative to 0 and swaps an inverted pair, so s.substring(offset, -1) there
quietly yields s[0..offset], the complement of the intended cut. Pass
s.len() as the end to mean “the rest”, and use
strip_prefix/strip_suffix (which return Option<str>) to drop an affix.
→ Strings
“mount: no element with id ‘…’”
mount or mount_root was given an id nothing on the page carries. The
host’s get_element_by_id hands back null typed as an Element, so the
shared lookup checks for that first and names the id, instead of leaving a
Cannot read properties of null to speak for itself. On a server-served
page, check_shell catches the same mismatch at boot.
→ Browser modules, Building UI