Package for Rust-like borrow checker in Julia?

@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.

5 Likes