How to use generalized finalization?

Hello

I’m trying out the new generalized finalization (Generalized Finalization (GNAT Reference Manual)) and already getting a GNAT bug with this code:

pragma Extensions_Allowed (On);

procedure Test is

   type Test_T is
      abstract tagged null record
      with Finalizable =>
        (Finalize => Finalize,
         Relaxed_Finalization => True);

   procedure Finalize (This : in out Test_T) is abstract;

begin

   null;

end Test;
> gnatmake test.adb
gcc -c test.adb
+===========================GNAT BUG DETECTED==============================+
| 15.0.1 20250329 (experimental) (aarch64-apple-darwin24.3.0)              |
| Assert_Failure failed precondition from einfo-entities.ads:3296          |
| Error detected at test.adb:11:49                                         |
| Compiling test.adb                                                       |
| Please submit a bug report; see https://gcc.gnu.org/bugs/ .              |
| Use a subject line meaningful to you and us to track the bug.            |
| Include the entire contents of this bug box in the report.               |
| Include the exact command that you entered.                              |
| Also include sources listed below.                                       |
+==========================================================================+

Please include these source files with error report
Note that list may not be accurate in some cases,
so please double check that the problem can still
be reproduced with the set of files listed.
Consider also -gnatd.n switch (see debug.adb).

test.adb

test.adb:11:14: warning: not dispatching (must be defined in a package spec) [enabled by default]
compilation abandoned
gnatmake: "test.adb" compilation error

Am I using it wrong?

As far I as read the feature, the type can be neither abstract nor tagged.

It can be tagged or abstract according to the docs. It just has to be the root type.

A couple things are wrong:

  1. Finalize itself cannot be abstract (though your record type can be abstract)
  2. Finalize needs the aspect No_Raise

As far as the bug, I would report it. The feature is new so they probably only tested their test cases. What I got to work on Godbolt was:

   package Test is

      type Test_T is
         abstract tagged null record
         with Finalizable =>
         (Initialize => Initialize,
          Adjust     => Adjust,
          Finalize   => Finalize,
          Relaxed_Finalization => True);

      procedure Adjust (This : in out Test_T) is null;
      procedure Initialize (This : in out Test_T) is null;
      procedure Finalize (This : in out Test_T) is null
         with No_Raise;

   end Test;

I needed to specify all the procedures or the compiler bug occurred. Their test case probably used all 3. I don’t know if all 3 are required or not (didn’t dig deep into the docs to see).

Note that the procedures don’t have to be null, you can give them actual implementations if you wish, they just cannot be abstract.

I did have to nest them in a package to avoid the bug as well, so you’ll want them to be in a package (not sure if a requirement or just part of the bug). But to properly establish primitive operations for a record type you want them in a package anyways.

With that my output is:

Compiler stdout

gcc -c -I/app/ -g -fdiagnostics-color=always -gnat2022 -gnatX -I- <source>
gnatbind -x example.ali
gnatlink example.ali -g -fdiagnostics-color=always -o /app/example

Program returned: 0
1 Like

Interesting, so the subprograms implementing the aspect for a tagged type would be primitive, overridable? What happens with controlled types? Would Finalize be called twice and once if not on the heap?

It looks like they can’t be derived off of controlled types as they would not longer be root types.

This feature also doesn’t handle finalization from heap allocated objects. It’s purely for simple cases. I assume since this is a really new feature, they wanted to start with a restricted rule set.

So, this is another point where my proposal (essentially abstractions for type-structures and type-usages) would help: if the conceptual type-hierarchy were reified with the “abstract type” and “abstract interface”, finalization would be easily addable at the base of the hierarchy.

This should be a non-issue TBH.
ALL types have finalization; true, this might be a null-procedure, and clean-up is popping the item from the stack (encapsulated with all other local entities of the frame). And some types might be a null-procedure, allocated into a subpool that is discarded altogether, which is exactly how you can view the frame. The co-derivation non-cleanup issue in GNAT exists because they failed to do such a thing.

Not an issue per se, but something to keep in mind otherwise, someone unsuspecting might find themselves with a memory leak or finalization operations that don’t run as expected (if they are dynamically allocating tagged objects using this feature and are used to how Controlled objects work).

Great improvement IMO. I have always been against this feature.

The bug has been fixed: Ada: Fix assertion failure on Finalizable aspect for tagged record type

1 Like