Negative Bounds
Status — accepted. Introduces
T: !Aspectas a bound that is satisfied whenTdoes not implementAspect. Required by RFC-0066 (Allocated Value Extraction) and RFC-0071 (Ownership and Move Semantics), both of which useT: !Drop.Corrected 2026-07-10, while integrating into the spec. §1 and §2.2's examples used pre-split bracket-channel allocator syntax (
@[r] T,[r]bracket parameters) — updated to the ratified value-channel form (@a T,(@a: A)). The bound mechanism itself is unaffected; only the allocator examples illustrating it were stale.
Status — integrated (2026-07-10). Integrated into public/reference/spec/declarations.md: T: !Aspect negative bounds. RFC's own stale @[r] bracket-channel syntax fixed first.
Status — implemented (2026-07-12). Enforced at all call sites + literal construction; issue #547
Summary
Aspect bounds (T: Aspect) assert that a type implements a given aspect. This RFC
introduces the complementary form: T: !Aspect asserts that a type does not implement
a given aspect. A negative bound is satisfied automatically when no implementation of the
named aspect exists for the type; it does not require any declaration at the implementation
site.
The primary motivating use is T: !Drop, which constrains move-out from bulk-deallocating
region allocators (RFC-0066 §2.2) and is implied by T: Copy (RFC-0071 §4). The
mechanism is general and applies to any aspect.
Motivation
RFC-0071 establishes that Copy and Drop are mutually exclusive: a type may not
implement both. The constraint "T has no Drop implementation" is already a meaningful and
checkable property — it is what makes bulk-deallocation safe in RFC-0066:
- A bulk-deallocating allocator (e.g.
Region) frees all backing memory at once when the region drops. It does not track individual allocations and cannot call per-element destructors at that point. - When a value
ptr: @[r] Tis moved out of such an arena, the slot is orphaned. The allocator has no record of the vacated slot. - If
T: Drop, the allocator would need to callT::dropon occupied slots at bulk-free time — but it cannot distinguish occupied from vacated slots. Moving out aT: Dropvalue therefore creates a situation where the Drop impl may run on already-evacuated memory. - If
T: !Drop, no destructor call is needed at bulk-free time. The backing memory may be reclaimed unconditionally.
There is currently no mechanism to express this constraint in a bound position. T: !Drop
cannot be written; only the absence of a Drop impl can be informally assumed. This RFC
provides the formal mechanism.
1. Syntax
A negative bound is written with ! before the aspect name, in any position where a
positive bound is accepted:
// generic parameter bound
fun move_out<T: !Drop, A: Alloc>(@a: A, ptr: @a T) -> T { … }
// multiple bounds — positive and negative may mix
fun transfer<T: Clone + !Drop, A: Alloc>(@a: A, src: @a T) -> T { … }
// struct field bounds (when conditional impls are in play, RFC-0036)
struct Arena<T: !Drop> { … }
The ! binds tightly to the aspect name. T: !Drop + Clone reads as
T: (!Drop) + Clone — T does not implement Drop, and T implements Clone.
2. Satisfaction
2.1 Concrete types
For a concrete type T, T: !Aspect is satisfied iff no implementation of Aspect for
T is reachable in the current compilation scope. This is the same lookup the compiler
already performs to check positive bounds — a negative bound simply inverts the result.
struct Point { x: f64, y: f64 }
// no impl Drop for Point — Point: !Drop is satisfied
struct Handle { fd: u64 }
extend Handle: Drop { fun drop(self: Handle) { close_fd(self.fd); } }
// impl Drop for Handle exists — Handle: !Drop is NOT satisfied
2.2 Generic types
In a generic context, T: !Aspect is not automatically assumed. The absence of a bound
does not imply the absence of an implementation — the type parameter may be instantiated
with any concrete type, including one that implements the aspect.
A function that requires T: !Drop must declare it explicitly:
// correct — T: !Drop is a stated requirement
fun extract<T: !Drop, A: Alloc>(@a: A, ptr: @a T) -> T { … }
// incorrect — T may or may not implement Drop; this is a type error if the
// body requires T: !Drop
fun extract<T, A: Alloc>(@a: A, ptr: @a T) -> T { … }
At the call site, the compiler verifies that the instantiated type satisfies the bound:
let ptr: @a Point = @a Point { x = 1.0, y = 2.0 };
extract(ptr); // Point: !Drop ✓
let ptr: @a Handle = @a Handle { fd = open("f") };
extract(ptr); // compile error: Handle implements Drop; Handle: !Drop not satisfied
2.3 The Copy implies !Drop rule
RFC-0071 §4 establishes that Copy and Drop are mutually exclusive. This is encoded as
an implication: any type satisfying T: Copy automatically satisfies T: !Drop. The
compiler derives this without any explicit declaration:
fun needs_no_drop<T: !Drop>(val: T) { … }
let p = Point { x = 1.0, y = 2.0 };
extend Point: Copy {} // Point: Copy
needs_no_drop(p); // valid — Point: Copy implies Point: !Drop
This implication is unconditional: there is no way to implement both Copy and Drop for
the same type, so the bound T: Copy is always a strictly stronger condition than T: !Drop.
2.4 Compound types
For compound types (structs, enums, tuples), T: !Drop is a claim about the type itself —
whether it has a Drop implementation — not about its fields. A struct whose fields
implement Drop does not itself implement Drop unless an explicit extend Struct: Drop
is provided:
struct Wrapper { inner: Handle }
// No impl Drop for Wrapper — Wrapper: !Drop is satisfied
// (Handle::drop will still run when Wrapper is dropped,
// via recursive field dropping — but Wrapper itself has no Drop impl)
This matches the semantics of RFC-0066: the concern is whether the slot in the arena
carries a destructor obligation. A Wrapper with no Drop impl has no such obligation for
the slot itself; field destructors run through the normal ownership chain before the arena
is freed.
3. Negative bounds in where clauses
Where clauses (when present in a function or impl block) accept negative bounds:
fun extract<T>[r](ptr: @[r] T) -> T
where T: !Drop
{ … }
This is equivalent to the inline form <T: !Drop>. The two forms may be mixed freely.
4. Interaction with conditional impls (RFC-0036)
RFC-0036 allows impl blocks that are conditional on type bounds. Negative bounds participate in these conditions on the same terms as positive bounds:
extend<T: !Drop> Arena<T>: BulkMove { … }
This impl applies only when T does not implement Drop. The compiler checks the condition
at each instantiation.
5. What negative bounds do not cover
5.1 Explicit negative impls
This RFC does not introduce a mechanism to explicitly declare that a type does not
implement an aspect (extend T: !Drop;). Negative bounds are checked against the
absence of a positive impl; they do not require any opt-out declaration at the type
definition site.
Explicit negative impls — needed to opt a type out of an auto-impl aspect that would otherwise fire — are specified in RFC-0081 (Negative Impls). The two mechanisms are distinct: negative bounds are use-site constraints; negative impls are impl-site declarations that affect what the negative bound check finds.
5.2 Negative bounds on the Self type within impl blocks
Coverage: untestable (see frontmatter). This section deliberately defers the feature and therefore specifies no observable behavior for a fixture to exercise.
Within an impl block, placing a negative bound on Self (e.g. to provide a method only
when the type does not implement some aspect) interacts with aspect coherence (RFC-0060) in
non-trivial ways. This is deferred until a concrete use case is established.
6. Unresolved questions
None.
References
- RFC-0034 (Struct-Enum-Aspect Bounds) — the bound syntax this RFC extends.
- RFC-0060 (Aspect Impl Coherence) — coherence rules that govern which impls are reachable and therefore relevant to negative bound checking.
- RFC-0066 (Allocated Value Extraction) — primary consumer of
T: !Drop; §2.2 specifies the move-out constraint that motivates this RFC. - RFC-0071 (Ownership and Move Semantics) — establishes
Copy/Dropmutual exclusion; §3–4 ground theCopy implies !Dropimplication. - RFC-0081 (Negative Impls) — explicit
extend Type: !Aspect;declarations that affect what negative bound checking finds; complement to this RFC.