Currently, finalizer
returns nothing, which requires simple constructors to be more verbose than necessary:
mutable struct A
v
A(x) = (a = new(x); finalizer(a, fn) ; a)
end
If finalizer(x, f)
where to return the object x
instead:
mutable struct B
v
B(x) = finalizer(new(x), fn)
end
I think this would improve readability for simple constructors.
Also, let's swap the arguments: finalizer(f, x)
. Of course, technically that's breaking since you could put a finalizer on a function, but it seems unlikely to come up in practice.
finalizer
is doing something to the object so it also makes sense to put the object it operates on as the first argument. Also I don't think it's very useful to bring up a breaking change that's already have it's own issue https://github.com/JuliaLang/julia/issues/16307 in another issue that's proposing a non-breaking change.
Except that we put functions as the first argument everywhere else.
Except that we put functions as the first argument everywhere else.
No. We do that when there's nothing else that has any reason to be in the first position, which is not the case here. A quick search shows other functions where we are not doing that includes sparsevec
.
Also, finalizer
is one of the few cases it should be strongly discouraged to use an anonymous function as input since that means it'll be very hard to do explicit cleanup. finalizer
also accepts pointer as the callback which does not make sense to be the first argument.
Also, finalizer is one of the few cases it should be strongly discouraged to use an anonymous function as input
maybe we should add a one-arg form?
finalizer(g) = (finalizer(g, finalize); g)
finalize
is never the right function to be the finalizer though...
Is there any chance of having type-based finalizers (#1037) rather than per-object ones (by v1.0)? I remember discussions about this in the past, but I don't remember if there was a consensus.
Proposal from triage:
finalize
finalizer
finalizer
finalizer
for "type-based finalization", which calls a default function, finalize!
API demo:
"""
destroy!(x)
Default method for `ondestroy(x)`.
"""
function destroy! end
"""
ondestroy(finalize::Any, @nospecialize x)
Calls `finalize(x)` when no references to `x` exist.
"""
function ondestroy(@nospecialize(finalize), @nospecialize x)
ccall(:jl_register_finalizer, Void, (Any, Any), x, finalize)
return x
end
"""
ondestroy(x)
Calls `destroy!(x)` when no references to `x` exist.
"""
ondestroy(@nospecialize x) = ondestroy(destroy!, x)
@deprecate finalizer(x, f) ondestroy(f, x)
@noinline function finalize(x)
depwarn("Manual execution of all finalizers is deprecated. Use type-based finalizer method `destroy!` instead.`, :finalize)
<original implementation>
end
Exploratory usage examples:
struct T
fields...
function T(fields...)
return ondestroy(new(fields...)) do
@async println("finalized object")
end
end
T() = ondestroy(new())
Base.destroy!(::T) = @async println("default finalizer called on object")
end
destroy!(T())
Other names considered: oncleanup / cleanup, defer / deferred / deferred!, ondelete
The originally stated proposal of this issue is non-breaking since it's rather unlikely that anyone would be relying on finalizer
returning nothing
.
OTOH this is easy to do and why not return the object?
This is done.
Most helpful comment
Except that we put functions as the first argument everywhere else.