If I have a Ptr
to an immutable struct, is there an easy way to access a single field?
Example:
struct Foo
x::Int
y::Float64
end
foo = fill(Foo(1,2))
p = pointer(foo)
julia> typeof(p)
Ptr{Foo}
It's easy to do this:
julia> unsafe_load(p)
Foo(1, 2.0)
But what can I do to load p->y
directly, without reading the entire struct (which might be large)?
How open to hacky ideas are you?
Because I have a definition of unsafe_getfield
in mind, but it's more than a few lines and not particularly clean
I'm open to hacky ideas if it's needed.
(But I would prefer a non-hacky solution if it's possible.)
It seems to me that unsafe_getfield
should be in Base.
I've done this before. The tricky part is efficiently getting the Symbol
-> fieldnumber mapping at compile time.
function unsafe_loadfield(p::Ptr{T}, i::Int) where {T}
FT = fieldtype(T, i)
offset = fieldoffset(T, i)
pfield = Ptr{FT}(p) + offset
unsafe_load(pfield)
end
@generated function unsafe_loadfield(p::Ptr{T}, s::Symbol) where {T}
names = fieldnames(T)
exprs = map(eachindex(names)) do i
quote
if s == $(QuoteNode(names[i]))
return unsafe_loadfield(p, $i)
end
end
end
Expr(:block, exprs..., :(error("Symbol $s is not a field of type $T")))
end
Oy! I got halfway through writing that :stuck_out_tongue_closed_eyes:
julia> unsafe_loadfield(p, :x)
1
julia> unsafe_loadfield(p, :y)
2.0
julia> @btime unsafe_loadfield($p, :y)
1.187 ns (0 allocations: 0 bytes)
2.0
julia> unsafe_loadfield(p, :z)
ERROR: Symbol z is not a field of type Foo
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:35
[2] macro expansion
@ ./REPL[25]:6 [inlined]
[3] unsafe_loadfield(p::Ptr{Foo}, s::Symbol)
@ Main ./REPL[25]:1
[4] top-level scope
@ REPL[29]:1
Nice! Thanks!
Rasmus Henningsson has marked this topic as resolved.
Rasmus Henningsson said:
If I have a
Ptr
to an immutable struct, is there an easy way to access a single field?
How do you have a pointer to an immutable struct, which isn't possible? What you showed is a pointer to a vector whose elements are instances of an immutable struct.
No, it's the pointer of a vector. The pointer he showed points to an immutable struct.
foo = fill(Foo(1,2))
p = pointer(foo)
pointer(array [, index])
Get the native address of an array or string, optionally at a given location
index.
It's a pointer to the "native address of" the vector, which happens to point to the immutable struct here. Does the language guarantee that this will be the case - that pointers to zero dimensional arrays will always be pointers to their element? One could imagine that not being the case (though in practice this might not ever happen).
The docs don't explicitly say it, but the "optionally at a given index" part seems to imply that it will get the address of the element at that index - so maybe it's slightly less hacky if we get the pointer as pointer(foo, 1)
instead.
Last updated: Apr 25 2025 at 04:43 UTC