← Back to AILP Home

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 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

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:

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:

  1. A unary-prefix value-sink expression: drop expr; (existing, pre-v0.29.x).
  2. A method name in the impl:Drop shape: 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