I have a julia object that contains some data fields that are initialized inside a ccall, so I am trying to write a finalizer to allow C to free the allocated memory, but I’m getting some weird behavior that I think might be a bug.
Here’s a MWE:
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.8.5 (2023-01-08)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
julia> mutable struct Foo
bar
end
julia> finalizer(Foo) do x
x.bar
return x
end
Foo
julia> y = Foo(1)
Foo(1)
julia> exit()
error in running finalizer: ErrorException("type DataType has no field bar")
So it seems like julia is forgetting what Foo is before calling the finalizer for Foo. If I try to assert x::Foo in the finalizer, I get this really wacky error:
julia> finalizer(Foo) do x
x::Foo
return x
end
Foo
julia> y = Foo(3); exit()
error in running finalizer: TypeError(func=:typeassert, context="", expected=Main.Foo, got=Main.Foo)
“error: expected Foo, got Foo…” seems like this isn’t working right, unless I have missed something critical about finalizers.
You’re registering the finalizer on the typeFoo, not an instance of the type y = Foo(1).
See the docstring:
help?> finalizer
search: finalizer UndefInitializer finalize
finalizer(f, x)
Register a function f(x) to be called when there are no program-accessible references to x, and return x. The type of x must be a mutable struct, otherwise the behavior of this function is
unpredictable.
f must not cause a task switch, which excludes most I/O operations such as println. Using the @async macro (to defer context switching to outside of the finalizer) or ccall to directly invoke IO
functions in C may be helpful for debugging purposes.
Examples
≡≡≡≡≡≡≡≡≡≡
finalizer(my_mutable_struct) do x
@async println("Finalizing $x.")
end
finalizer(my_mutable_struct) do x
ccall(:jl_safe_printf, Cvoid, (Cstring, Cstring), "Finalizing %s.", repr(x))
end
A finalizer may be registered at object construction. In the following example note that we implicitly rely on the finalizer returning the newly created mutable struct x.
Example
≡≡≡≡≡≡≡≡≡
mutable struct MyMutableStruct
bar
function MyMutableStruct(bar)
x = new(bar)
f(t) = @async println("Finalizing $t.")
finalizer(f, x)
end
end
Your example would be
julia> mutable struct Foo
bar
function Foo(bar)
y = new(bar)
finalizer(y) do x
@show x.bar
return x
end
return y
end
end
julia> y = Foo(1)
Foo(1)
julia> finalize(y)
x.bar = 1