# delete_routine

Definition: | object o = delete_routine(object o, integer rid)
Description: | Associate a cleanup routine with some data, to be invoked when it gets destroyed.
pwa/p2js: | Not supported. JavaScript does not (even) have class destructors, and trying to place any kind of event listener on a JavaScript object would require a reference, which would in turn prevent it from ever being garbage collected, which is of course the very event you would like/need to trap. [DEV there is however, I just found, a FinalizationRegistry... (see https://javascript.info/weakref-finalizationregistry or better yet google/MDN.]
Comments: | When the reference count on a data object drops to zero it is automatically garbage collected. This routine allows additional processing to occur at the point when that happens, which may include freeing some allocated memory, closing a file, releasing some other resource, or removing indexes that point to an item. It is also permissible to explicitly invoke delete() (or even free(), which behaves identically when a delete_routine is in force) on remaining objects ahead of their eventual destruction, to force any such additional processing to occur immediately, or in a specific order. However the same effect can also be achieved by setting the [last copy of] o to NULL, or simply removing some table entry containing said. It is also worth pointing out that using free() or delete() on an allocated memory address is guaranteed to leave an invalid memory pointer lying about, whereas when relying on automatic cleanup the memory will still be there, even when ideally it shouldn’t be, until it is guaranteed safe to reclaim, and hence not cause unpredictable memory corruption should it accidentally be accessed after being free()d. The routine specified by rid should be a procedure that takes a single parameter, being the object to be cleaned up. It is usually the case that you would apply delete_routine() to individual table elements rather than a whole table. The data passed to delete_routine should whenever possible be "final". Appending, prepending, or replacing elements of data after delete_routine has been applied may or may not invoke the cleanup routine as you might hope (invocation depends mainly on the triggering of an internal clone operation, perhaps because of insufficient space or multiple reference counts). An error occurs if a delete_routine has already been set. This is inconsistent with Euphoria, which allows daisy-chains of cleanup routines, however my argument is that it is better to prevent routines such as unlink (below) from being invoked twice, than it is to permit the added but questionable extra flexibility. Besides there is nothing to prevent the data from containing a list of routine_ids that the one associated cleanup routine can invoke in turn, and if you put unlink in that list twice it is your problem not mine, but at least it is trivial to scan such a list, something you would struggle to do with multiple hidden internal associations. An error also occurs when an attempt is made to apply a routine_id to a constant, with the exception of integer constants, because the run-time simply cannot determine whether an integer value such as 4 is originally from open() or MB_YESNO - however I reserve the right (albeit with no actual intention or schedule) to add compile-time errors for the latter at some future date. Obviously invoking, say, delete_routine("test",rXXX) is utterly pointless - the constant "test" is never going to be destroyed, and hence rXXX would never be invoked. This is also inconsistent with Euphoria. Should an integer be passed as the first parameter, it is promoted to atom. This is required for memory allocations, as some may fall below the magic #3FFFFFFF boundary. Note this may affect some comparison operations, as internally the run-time (eg builtins\VM\pJcc.e) assumes that integers and floats are never equal. It is legal to invoke delete_routine(<object>,0). This has the effect of removing any association without invoking it. It has the further effect of resetting the pHeap link, see technicalia. The routine_id is validated to ensure it is not -1, but only when the first parameter is non-integer. Some memory allocation routines offer default delete_routines as part of automatic memory management.
Example: |
    sequence list = {}
    intlisthead = 0

    procedure unlink(sequence item)
        if item[PREV]=0 then
            listhead = item[NEXT]
        else
            list[item[PREV]][NEXT] = item[NEXT]
        end if
        if item[NEXT]!=0 then
            list[item[NEXT]][PREV] = item[PREV]
        end if
        close(item[FILEHANDLE])
        free(item[MEMPTR])
    end procedure

    procedure link(sequence item)
        item[PREV] = 0
        item[NEXT] = listhead
        list = append(list,delete_routine(item,routine_id("unlink")))
        if listhead!=0 then
            item[listhead][PREV] = length(list)
        end if
        listhead = length(list)
    end procedure
Implementation: | via :%opDelRtn in builtins\VM\pDeleteN.e (an autoinclude) - be warned however it is low-level complicated stuff that you do not need to know.
See Also: | delete, allocate
Technicalia | This mechanism is not guaranteed to play nice with automatic memory leak checking. In the above example, the list items also hold chain links, but if separate tables were used it might be the case that shutdown could destroy the link table before invoking unlink on some item in another table. It may prove necessary for automatic memory leak checking to trigger errors when any outstanding delete_routine calls are detected. Tip: When testing, especially in top-level code, it can be helpful to always store the result of delete_routine in a named variable. Code such as `myproc(delete_routine(..))` or even `table = append(table,delete_routine(..))` will store the result in an unnamed temporary variable, and it may prove difficult if not impossible to eradicate such hidden reference counts, thus invalidating the test. For instance you might need something like this:
        object tmp = delete_routine(thing,routine_id(xxx))
        table = append(table,tmp)
    --  table = table[1..$-1]   -- probably not, but...
        tmp = 0                 -- now we can expect cleanup routines to trigger
        table = table[1..$-1]   -- ...that should do it
Of course in non-top-level code, if you invoke a routine, say `link()`, then by the time `link()` returns, any such hidden temporaries will have been destroyed, and any associated reference counts appropriately decremented. Also, routines associated/invoked via delete_routine must not invoke task_yield().