doc/noalloc.md
Rama master · réplica de lectura del repositorio original.
# Allocation contracts
`@[noalloc]` checks a V function and every function body reachable through its direct calls.
The compiler reports allocation sites with the root function, call chain, and source location.
Checks run during ordinary compilation and `-check`, including instantiated generic functions.
Allocations in startup code or unrelated functions do not violate the contract.
```v
@[noalloc]
fn factorial(n int) int {
if n < 2 {
return 1
}
return n * factorial(n - 1)
}
```
Checks cover source operations and the compiler's lowered code. Dynamic array and map creation,
string construction, interface and sum-type boxing, escaping local storage, closures, and
allocations inside called functions are rejected. An annotation on a V callee does not hide its
body: the compiler still checks it. Cached imports are read from source when their bodies are
needed for allocation checks. Recursion is checked without revisiting the same body.
Boolean short-circuit operators, string header copies and returns, and zero-copy string views
such as `unsafe { tos(ptr, len) }` do not allocate. Constructing strings by concatenation,
interpolation, or cloning remains checked. Struct literals check only defaults for fields that
are not explicitly initialized; literal defaults, including signed numeric constants and
`unsafe { nil }`, are allowed.
This check is conservative. Operations whose allocation behavior cannot be proved are rejected,
even when a particular execution would allocate nothing. This includes indirect function calls,
interface dispatch, bound method values, unexpanded compile-time code, custom operators, and
copies or returns of owning aggregates. The standalone FastC backend rejects V allocation contracts;
use the C backend. Contracts on function aliases and interface declarations are unsupported and
produce an error. Annotate V or C function declarations instead.
Automatic ownership and cleanup (`-ownership`, `-d ownership`, `-autofree`), profiling
(`-prof`, `-profile`), call tracing (`-trace-calls`), and coverage (`-coverage`) are rejected
for annotated functions. These modes can introduce callbacks or instrumentation after the
allocation checks, so their generated behavior cannot establish this contract.
## Reusable output buffers
By default, appending to an existing mutable array parameter is allowed. Such a buffer can grow
until it reaches its high-water mark. The exception covers only that buffer's growth: constructing
an appended dynamic array, slicing a fixed array, or boxing an interface element still fails.
Bulk append is rejected because an aliasing source can require a separate temporary copy.
```v
@[noalloc]
fn write_byte(mut out []u8, value u8) {
out << value
}
```
Use `@[noalloc: strict]` to forbid buffer growth as well. Other attribute arguments are invalid.
## Foreign functions and termination
C declarations are assumed to allocate unless explicitly annotated with `@[noalloc]`. The
annotation is a trusted foreign contract, so the declaration's author must verify it. Standard
memory copying, comparison, searching, freeing, and string-length primitives have these contracts.
Calls to the known `panic`, `exit`, `C.exit`, and `C.abort` functions are exempt, including their
arguments, because they terminate execution. Statements before a terminating call remain checked;
an earlier branch that returns successfully cannot bypass the contract.
Hoisted argument allocations are exempt only for a source-validated body consisting solely of
the terminating call. More complex terminating control flow can be rejected conservatively.
Cached V declarations without an available body cannot establish a nonallocation proof. The
compiler rejects such calls rather than trusting an annotation in a cached declaration header.