Converting Ptr of subtype to Ref of supertype in ccall

My question comes down to, should this be an error:

julia> Base.cconvert(Ref{Real}, Ptr{Integer}(1))
ERROR: MethodError: Cannot `convert` an object of type Ptr{Integer} to an object of type Real
This may have arisen from a call to the constructor Real(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] Base.RefValue{Real}(::Ptr{Integer}) at .\refpointer.jl:42
 [2] convert(::Type{Ref{Real}}, ::Ptr{Integer}) at .\refpointer.jl:53
 [3] cconvert(::Type{T} where T, ::Ptr{Integer}) at .\essentials.jl:306

In the documentation it states that:

Here, the input p is declared to be of type Ref{gsl_permutation}, meaning that the memory that p points to may be managed by Julia or by C. A pointer to memory allocated by C should be of type Ptr{gsl_permutation}, but it is convertable using Base.cconvert() and therefore can be used in the same (covariant) context of the input argument to a ccall.

I do follow that, but now I have the additional complication that I want to also allow Refs to subtypes. Mockup example:

abstract type Mammal end
abstract type Bear <: Mammal end

# here is a pointer that comes from a ccall
Ptr{Bear}(1)

# now it needs to go into a function that should work for any Mammal
# and since memory might also be owned by Julia, it is Ref
function staywarm(m::Ref{<:Mammal})
    # so far so good, the Ptr{Bear} can enter this function
    ccall(:zoo, Void, (Ref{Mammal},), m)
    # but it cannot do the ccall since it will fail to Base.cconvert(Ref{Mammal}, m)
end

Looking in essentials.jl I see this hits the top line. If I change the Ptr to Ref in the second line it does work for me, but seems to cause chaos in the test suite.

cconvert(T::Type, x) = convert(T, x) # do the conversion eagerly in most cases
cconvert(::Type{<:Ptr}, x) = x # but defer the conversion to Ptr to unsafe_convert

You can’t type the ccall with a Ref to an abstract type. What is C supposed to do with an Abstract Julia type?
I think this can be changed to:

function staywarm(m::Ref{T}) where T <: Mammal
    ccall(:zoo, Void, (Ptr{T},), m)
end

Of course this means, that there must be a zoo function in C that works with all mammals…

Thanks Simon, the (Ptr{T},) works indeed, but is that safe if the memory is owned by Julia? If I try your solution with (Ref{T},) I get:

error compiling staywarm: ccall: argument type Ref should have an element type, not Ref{T}

In the end they are all just opaque pointers, but with the abstract types I try to tell Julia that functions like this:

Can accept subtypes of GDALMajorObjectH, which they can also in the C library.

Because Julia is told it is a subtype:

This is working well as-is, but I wanted to switch the input types from Ptr{T} to Ref{T} because I believe it’s more general and possibly allow Julia owned objects to be used as input. Or am I mistaken in this?

In a cruel turn of events, Ref is actually an abstract type! :wink:

No, I meant how is ccall supposed to work with an Abstract type? The annotation in ccall is for annotating the function 1:1 like the types that you would put into a C header file - and you wouldn’t (usually) find an abstract Julia type in a C header.
So since your C function expects a pointer, you need to use Ptr and not Ref.
The pair of ccall_type::Ptr{T} => ccall_arg::Ref{T} is already the semantic for using Julia owned objects.
So Ref is just a container for a Julia object which can be savely converted to a C Ptr, while still being owned by Julia.
That’s also why Ref is abstract, because different julia object need different Ref types (e.g. an Array).

Many thanks, it’s starting to dawn on me. If it should be 1:1 I should just always use Ptr{Void} as the ccall input type right? Since in C these are all typedef void *

function staywarm(m::Ref{<:Mammal})
    ccall(:zoo, Void, (Ptr{Void},), m)
end

And then if I want to use it with the right C-compatible Julia structs instead:

struct Cat <: Mammal  # I believe Simon likes cats
    a::UInt8
    b::Float64
end

Then I can use this function as follows:

a_cat = Cat(0x01, 2.0)
ref_cat = Ref(a_cat)
staywarm(ref_cat)