Cannot delete a variable in a macro

I want to simulate a allocation system trough the two following functions

alloc_float(n) =  unsafe_wrap(Array, convert(Ptr{Float64},Libc.calloc(n, 8)), n)
macro free(p)
   :(Libc.free(pointer($p)); p = nothing) # does not work with $p = nothing
end

If I used the 2 functions

julia> w = alloc_float(3)
3-element Vector{Float64}:
 0.0
 0.0
 0.0

julia> @free w

julia> w
3-element Vector{Float64}:
 2.86883399e-315
 2.90896374e-315
 1.6e-322

the w still remains in the Main space variable even if it points on bad memory area.
The question here is about the macro and why it does not work, not about the pertinence of that two functions.

It has to do with how Julia handles macro hygiene, expression evaluation order, and variable reassignment in outer scopes. $p correctly interpolates the variable name w into pointer(w). However, p = nothing inside the quote block creates a local binding inside the expanded code block that doesn’t bind back to the caller’s variable w.

So, escaping is needed for memory de-allocation and re-binding in the calling context.

macro free(p)
    quote
        Libc.free(pointer($(esc(p))))
        $(esc(p)) = nothing
    end
end

That will make

@free w

return nothing.

I found the following solution

macro free(p)
  return eval(:(Libc.free(pointer($p)); $p = nothing))
end

but I prefer yours.

That does something completely different. 99 times out of 100, if you use eval in a macro you are doing something very wrong!

I’d just like to point out the most important tool for writing macros, in my experience: @macroexpand, which shows you the code that the macro actually generates. In this case, using the original macro I see

julia> @macroexpand @free w
quote
    (Main.Libc).free(Main.pointer(Main.w))
    #= REPL[2]:2 =#
    var"#29#p" = Main.nothing
end

That var"#29#p" is your clue that that second statement isn’t doing what you’d want, because var"#29#p" is just some automatic variable name that doesn’t mean anything useful here.

Now, when we define and inspect @technocrat’s version:

julia> macro free(p)
           quote
               Libc.free(pointer($(esc(p))))
               $(esc(p)) = nothing
           end
       end
@free (macro with 1 method)

julia> @macroexpand @free w
quote
    #= REPL[4]:3 =#
    (Main.Libc).free(Main.pointer(w))
    #= REPL[4]:4 =#
    w = Main.nothing
end

We can see that w is correctly written there in both places we want it to be referenced.

Thanks for that very clear explanation. Metaprogramming is often hard to understand and debug, although it is totally deterministic.