@mbauman nice recommendation – it works!! Check this out:
julia> using BorrowChecker
julia> @bind x = 1
Bound{Int64}(1)
julia> f(x::Int) = x^2
f (generic function with 1 method)
julia> result = BorrowChecker.managed() do
# Enters Cassette.@overdub context
f(x)
end
1
julia> x # Successfully moved
[moved]
It succesfully called @take(x)
on our bound variable x
despite us not doing anything.
The code was surprisingly concise too:
maybe_take!(x) = x
function maybe_take!(arg::AllBound)
is_moved(arg) && throw(MovedError(arg.symbol))
value = unsafe_get_value(arg)
mark_moved!(arg)
return value
end
const SKIP_METHODS = (
Base.getindex, Base.setindex!, Base.getproperty,
Base.setproperty!, Base.getfield, Base.setfield!
)
function skip_method(f)
parentmodule(parentmodule(f)) == parentmodule(@__MODULE__) || f in SKIP_METHODS
end
Cassette.@context ManagedCtx
function Cassette.overdub(ctx::ManagedCtx, f, args...)
if skip_method(f)
Cassette.fallback(ctx, f, args...)
else
Cassette.recurse(ctx, f, map(maybe_take!, args)...)
end
end
function managed(f)
Cassette.@overdub(Cassette.disablehooks(ManagedCtx()), f())
end
That’s it!!
All it does is: goes through the arguments of EVERY SINGLE external function call, run maybe_take!
on the arguments, and then call the function. This results in all Bound
and BoundMut
objects being taken, which voids their ownership in their original scope, thus
julia> x == 2
ERROR: Cannot use x: value has been moved
i.e., it is no longer owned, and can’t be used!
The only functions where x
won’t be taken are ones we have defined explicitly. Otherwise we assume all external functions take ownership unless specified otherwise.