← board

Initialize() / Finalize() standard procedures (managed-type intrinsics)

Problem

FPC/Delphi define Initialize(v) / Finalize(v[, count]) standard procs: treat v (a variable of a managed type, or a record/array containing managed fields) as raw memory and set it to the initialized-empty state (Initialize), or release its managed contents (Finalize). Canonical use:

{ TLinkedListItem.GetCopy raw-Moves all instance data -> the ansistring
  field now aliases the original without a refcount bump. Reinit the field
  as if it were fresh memory, then assign properly: }
Initialize(TCmdStrListItem(Result).FPStr);
TCmdStrListItem(Result).FPStr := FPstr;

pxx has the underlying machinery (managed locals get init/final generated by the compiler) but does not expose it as callable intrinsics — grep for an Initialize builtin comes up empty.

PREMISE RE-MEASURED — half of it is wrong, and the wrong half is worse

(frank2, 2026-08-17, at 2611fb398. Re-measured before starting, per the standing rule that a ticket's cause section ages faster than its symptom section. Did not start the implementation — see the note at the end.)

Initialize is indeed absent, and fails honestly: error: undefined variable (Initialize).

Finalize is NOT absent. It is a silent no-op. parser.inc:23126 matches the name, skips to the matching ) with a paren counter, and emits GenMakeSeq(-1, -1) — an empty sequence. The call is accepted, the argument is never even parsed as an expression, and nothing happens.

Measured against FPC 3.2.2:

s := 'hello';  t := s;  Finalize(s);
WriteLn('s = [', s, ']');   { FPC: s = []      pxx: s = [hello] }
WriteLn('t = [', t, ']');   { FPC: t = [hello] pxx: t = [hello] }

So this is not only a missing feature. Under Rene's dialect rule — a refusal beats a wrong value — the Finalize half is a bug, and the compat escape rule in CLAUDE.md says a parity finding that means silent wrong behaviour gets promoted out of the compat/feature bucket. The cclasses idiom this ticket exists to serve (raw Move, then reinit the managed field) is exactly where a no-op costs a leak or a double free rather than a diagnostic, and neither of those points back at this line.

Whoever takes this should decide the refusal question first, because it is separable from and cheaper than the feature: making Finalize an error until it is implemented is a few lines and removes a silent wrong value today. Against it: existing code may already call Finalize and rely on it compiling (that is what a no-op buys), so turning it into an error could break a build that currently "works". That trade is a Track U call, not mine — file decide-finalize-noop-vs-refusal if it is not obvious to you.

Not started: implementing the pair means touching managed init/final generation and refcount semantics, which is the silent-corruption class of change, and this was measured at the end of a long session. Banking the correction rather than half-landing the feature.

Fix shape

Two intrinsics resolving against the argument's static type:

Gate

make test + self-host byte-identical. Regressions: Move-then-Initialize refcount pattern (mirror the cclasses idiom) leaks nothing and double-frees nothing under the ansistring refcount checks; Finalize on a record with mixed managed/unmanaged fields.

2026-08-21 — RESOLVED by [[feature-a-implement-initialize-and-finalize-over-the-arc-helpers]]

Closed rather than worked, as that ticket instructed. Its premise — that the intrinsics were missing — was half wrong: Finalize was parsed and its arguments discarded (an empty sequence emitted), which is worse than missing, because FPC-shaped code compiled and silently did nothing. Initialize genuinely did not exist.

This ticket's own canonical example now matches FPC exactly. The TLinkedListItem.GetCopy shape — raw-Move a record containing an AnsiString, so the copy aliases the original with no refcount bump, then Initialize the alias before assigning through it — run against FPC 3.x -Mobjfpc -O-:

a=[orig] b=[copy] keep=[orig]
b after fin len=0 a still=[orig]

Identical on both. The original survives the copy's Finalize, which is the property the whole idiom depends on.

Scope note for [[goal-compile-fpc-compiler]], which this listed as a blocker: records and AnsiStrings are done; a bare dynamic-array or variant lvalue is still refused with a compile error (deliberately, not a no-op) and is [[feature-a-finalize-for-bare-dynarray-and-variant]]. cclasses.pas:2394 is a record-with-managed-field case, so it is covered.

The track line here said "Track P parse; lowering touches Track A" — in the end it was all Track A: the parse desugars a string Finalize into an assignment and everything else is IR plus two builtinheap helpers.