Surface
This page describes the source-level surface for Drop: what
you write in a .npk file to opt in, what shapes are accepted
by the parser and sema, and what is intentionally not on the
surface this cycle.
Opt-in: import drop.npk
use "drop.npk".*;
That's it. There is no per-binding annotation. The import
brings five sentinel structs and their Drop impls into scope;
the compiler keys on the presence of those impls in the
program's Drop registry and flips per-region flags
(wild_raii_enabled, wildx_raii_enabled,
handle_arena_raii_enabled, jit_fn_raii_enabled,
handle_raii_enabled). The fifth sentinel, NitpickHandleRaii,
landed in v0.31.5.x and flips per-Handle<T> auto-free
independently from the arena-level flag (HANDLE-DEC-002).
Without the import, the four flags stay off and the compiler emits no automatic drops. Existing programs are unaffected.
Defining a Drop impl
struct:Holder = {
int64:value,
};
impl:Drop:for:Holder = {
func:drop = NIL($m Holder:self) {
pass;
};
};
Requirements (enforced by sema in v0.29.1):
- The trait name must be
Drop. No other trait can be declared with this shape. - The type must already exist (no lazy fabrication via the primitive-type path — sema rejects unknown type names with a clean error).
- Exactly one method, named
drop. - Signature: returns
NIL, takes a single$m T:selfborrow. - Duplicate
impl:Drop:for:Tdeclarations are rejected.
The compiler registers T_drop (mangled) in the Drop registry.
What the body can do this cycle
The four NitpickXxxRaii sentinel drop methods in
stdlib/drop.npk all have a body of just pass;. That is
deliberate: the compiler does not call the sentinel drop
method directly. The sentinel exists only to flip the per-region
flag; the actual cleanup is the hand-emitted
npk_free / npk_wildx_free / npk_handle_arena_destroy call.
For user types with impl:Drop:for:T, the surface is in
place (parser, sema, registry) but codegen for user-defined
drop dispatch is out of scope for v0.29.x. Writing a real
body in a user drop method will not produce auto-dispatch
this cycle; the user must continue to call T_drop explicitly
if they want the body to run. The four built-in regions are
the only auto-cleanup this cycle ships.
What is not on the surface
impl:Drop:for:on primitives (int32,bool, …). Sema rejects via the "type does not have a struct decl" check. Seefaq.md.- A user-callable
drop xexpression that runs a type's Drop impl. The keyworddropis reserved as a unary value-sink (drop x;evaluatesxand discards the result, bypassing TBB Result handling — predates v0.29.x), andc.drop()UFCS sugar is parser-accepted but does not dispatch through the Drop registry this cycle. rawoverrides of automatic Drop. Perdrop.npk, the planned opt-out spelling israw npk_free(p);(manual free that suppresses the auto-drop on the same binding). v0.29.x ships the auto-emit but does not yet diagnose the explicit-free + auto-drop collision; that detection is the v0.29.x successor cycle's work. In v0.29.x, writingnpk_free(p);for a binding the compiler also auto-drops is a double-free — don't do it.impl:Dropon generic types. Sema-level only; generics are tracked in a separate cycle.
Per-binding opt-out with nodrop
Landed in v0.31.6.x (W-04, NODROP-DEC-001..013): the
nodrop keyword wraps an initializer at the declaration site
and tells both the borrow checker and the IR generator to
skip RAII enrolment for that single binding. The binding
keeps the legacy explicit-free contract — the user is
responsible for calling the matching free / destroy.
use "drop.npk".*;
use "handle.npk".*;
func:main = int32() {
int64:a = raw HandleArena.create();
// RAII-managed (default): IR-gen emits HandleArena.destroy(a)
// at scope end; explicit destroy would be ARIA-022.
Handle<int64>:h = nodrop raw HandleArena.alloc(a, 8i64);
// Opted out: borrow checker skips `local_handles_` enrolment
// and IR-gen skips the auto `npk_handle_free` emit. The user
// MUST call `raw HandleArena.free(h);` themselves, otherwise
// ARIA-014 fires for a missing free.
raw HandleArena.free(h);
exit 0;
};
Key rules:
- Outer-only (NODROP-DEC-011).
nodropis consulted at the outermost initializer position.identity(nodrop f())does NOT opt the binding out; this is intentional and prevents action-at-a-distance through helper functions. - Composes with
rawanddrop. Each wrapper peels its own outermost layer, sonodrop raw alloc(N)is the idiomatic spelling when you want to both unwrap aResult<T>and opt out of RAII. - No-op on plain values. Applying
nodropto an expression whose binding is not a recognised RAII region (e.g.int64:x = nodrop 42i64;) is silently accepted and has no codegen effect. - Diagnostic interaction. Explicit-free on a
RAII-managed binding is
ARIA-022; the hint text mentionsnodropas the per-binding fix.
See pitfalls.md for the precise outer-only
rule and the ARIA-022 collision examples.
Per-function NLL timing with #[nll_drop]
Landed in v0.31.7.x (W-07, NLL-DEC-001..014): the
#[nll_drop] attribute on a function declaration retimes
that function's auto-emitted RAII drops from lexical scope
end to the last-use point of each enrolled binding,
computed by backward CFG liveness. A per-binding
#[lexical_drop] attribute opts a single binding back into
scope-end timing inside an #[nll_drop] function. Both are
no-ops on bindings not enrolled in RAII.
#[nll_drop]
func:hot_loop = NIL() {
Handle<int64>:scratch = raw HandleArena.alloc(a, 64i64);
use_scratch(pass scratch); // drops here
expensive_pure_computation(); // scratch already freed
};
The attribute is orthogonal to nodrop: nodrop opts out
of RAII enrolment, #[lexical_drop] opts out of NLL
timing. They compose. The annotation also triggers an
ARIA-052 warning when applied to a function with no RAII
bindings to retime. See nll.md for the full
cookbook.
Parser interaction with the drop keyword
drop is both:
- A unary-prefix value-sink expression:
drop expr;(existing, pre-v0.29.x). - A method name in the
impl:Dropshape:func:drop = NIL($m T:self) { ... };.
DROP-DEC-007a (v0.29.0) resolved the parser collision by
accepting TOKEN_KW_DROP as a method-name identifier inside
parseFuncDecl and as a member-name identifier inside
parseMemberExpression. The existing drop <expr> unary form
is unchanged.
Validation
- bug279–bug284 (v0.29.1) — surface acceptance and rejection.
- The four
NitpickXxxRaiiimpls instdlib/drop.npkare themselves the canonical examples.