Stream: helpdesk (published)

Topic: ✔ Access field of Ptr to struct


view this post on Zulip Rasmus Henningsson (Apr 24 2025 at 13:00):

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)?

view this post on Zulip Timothy (Apr 24 2025 at 13:09):

How open to hacky ideas are you?

view this post on Zulip Timothy (Apr 24 2025 at 13:11):

Because I have a definition of unsafe_getfield in mind, but it's more than a few lines and not particularly clean

view this post on Zulip Rasmus Henningsson (Apr 24 2025 at 13:28):

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_getfieldshould be in Base.

view this post on Zulip Mason Protter (Apr 24 2025 at 13:34):

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

view this post on Zulip Timothy (Apr 24 2025 at 13:35):

Oy! I got halfway through writing that :stuck_out_tongue_closed_eyes:

view this post on Zulip Mason Protter (Apr 24 2025 at 13:36):

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

view this post on Zulip Rasmus Henningsson (Apr 24 2025 at 13:37):

Nice! Thanks!

view this post on Zulip Notification Bot (Apr 24 2025 at 13:38):

Rasmus Henningsson has marked this topic as resolved.

view this post on Zulip Mosè Giordano (Apr 24 2025 at 21:22):

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.

view this post on Zulip Mason Protter (Apr 25 2025 at 04:00):

No, it's the pointer of a vector. The pointer he showed points to an immutable struct.

view this post on Zulip Sundar R (Apr 25 2025 at 04:13):

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

view this post on Zulip Sundar R (Apr 25 2025 at 04:14):

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