From the documentation, the answer appears to be a fairly clear "no".
Firstly, as you likely know (and indeed might guess from the name), Base.unsafe_convert provides no safety on this front, with the docstring warning
Be careful to ensure that a Julia reference to x exists as long as the result of this function will be used
The existence of a pointer alone will certainly not provide that. Worse, as the docstring for Base.cconvert notes
Neither convert nor cconvert should take a Julia object and turn it into a Ptr.
What is the recommended way to take a Julia object and turn it into a Ptr? That would seem to be Base.pointer, which provides a bit more useful information in its documentation
help?> Base.pointer
pointer(array [, index])
Get the native address of an array or string, optionally at a given location index.
This function is "unsafe". Be careful to ensure that a Julia reference to array exists
as long as this pointer will be used. The GC.@preserve macro should be used to protect
the array argument from garbage collection within a given block of code.
Calling Ref(array[, index]) is generally preferable to this function as it guarantees
validity.
Notably, the pointers returned by
julia> s = "some string"
"some string"
julia> Base.pointer(s)
Ptr{UInt8} @0x000000010d4b2838
julia> Base.unsafe_convert(Ptr{UInt8}, s)
Ptr{UInt8} @0x000000010d4b2838
julia> Base.unsafe_convert(Ptr{UInt8}, Base.cconvert(Ptr{UInt8}, s))
Ptr{UInt8} @0x000000010d4b2838
are identical.
A bit more useful information can be found in the Base.GC.@preserve docstring in turn:
help?> Base.GC.@preserve
GC.@preserve x1 x2 ... xn expr
Mark the objects x1, x2, ... as being in use during the evaluation of the expression
expr. This is only required in unsafe code where expr implicitly uses memory or other
resources owned by one of the xs.
Implicit use of x covers any indirect use of resources logically owned by x which the
compiler cannot see. Some examples:
• Accessing memory of an object directly via a Ptr
• Passing a pointer to x to ccall
• Using resources of x which would be cleaned up in the finalizer.
@preserve should generally not have any performance impact in typical use cases where
it briefly extends object lifetime. In implementation, @preserve has effects such as
protecting dynamically allocated objects from garbage collection.
Examples
≡≡≡≡≡≡≡≡≡≡
When loading from a pointer with unsafe_load, the underlying object is implicitly
used, for example x is implicitly used by unsafe_load(p) in the following:
julia> let
x = Ref{Int}(101)
p = Base.unsafe_convert(Ptr{Int}, x)
GC.@preserve x unsafe_load(p)
end
101
When passing pointers to ccall, the pointed-to object is implicitly used and should be
preserved. (Note however that you should normally just pass x directly to ccall which
counts as an explicit use.)
julia> let
x = "Hello"
p = pointer(x)
Int(GC.@preserve x @ccall strlen(p::Cstring)::Csize_t)
# Preferred alternative
Int(@ccall strlen(x::Cstring)::Csize_t)
end
5
So, some take-home points
- A pointer returned by
Base.pointer(s) to a string will be safe if and only if either (a) there is a Julia reference to that string s later in the code or (b) you have used Base.GC.@preserve
- A pointer returned by
Base.unsafe_convert(Ptr{UInt8}, Base.cconvert(Ptr{UInt8}, s)) appears to have all the same caveats as a pointer returned by Base.pointer, with the added bonus of a cryptic and ominous warning.
- There is no indication that the type of Julia object (
String vs Array vs etc.) matters.