How to get reference to the field of mutable struct

How one can create the reference to the field of the instantiated mutable struct?

Let’s say I have a mutable struct A defined as

mutable struct A
    v1::Int
    v2::Float64
end

and an instance of this structure a = A(0, 0.0).

I would like to get reference ref_a_v1 to the field v1 of object a, so that

ref_a_v1[] = 4

will result in changing the value of a.v1 to 4.

At the beginning, I thought Ref(a.v1) would work, but this obviously won’t work, since it is equivalent to Ref(0), assuming that a.v1 = 0.

At this point, the only idea how I can handle it is creating mutable struct with references as a fields, i.e.,

mutable struct A
    v1::Ref{Int}
    v2::Ref{Float64}
end

however, it does not seem like a appropriate solution.

1 Like

How do you feel about this?

julia> a = A(0, 0.0)
A(0, 0.0)

julia> ref_a = Ref(a)
Base.RefValue{A}(A(0, 0.0))

julia> ref_a[].v1 = 5
5

julia> a
A(5, 0.0)

It’s not as direct as what you originally wanted, but it seems to do what you want.

I am aware of the approach you proposed, but unfortunately, it does not solve my case, since I need pass some struct field’s references into ccall.

At this moment, I figure out that I could alternatively use Ptr.

mutable struct A
    v1::Int
    v2::Float64
end

a = A(1, 0.0)

p_a = Ptr{A}(pointer_from_objref(a))
p_v1 = Ptr{Int}(p_a)
p_v2 = Ptr{Float64}(p_a + sizeof(Ptr))

unsafe_store!(p_v1, 45)
unsafe_store!(p_v2, 100.0)
a

However, this approach is fragile, since it depends on the order of fields in struct A.

1 Like

However, this approach is fragile, since it depends on the order of fields in struct A.

You can use fieldoffset instead if sizeof to make the pointer calculation robust. And when you are at it, you should probably also use fieldtype.

Then your two lines would be changed to

p_v1 = Ptr{fieldtype(A, 1)}(p_a + fieldoffset(A, 1))
p_v2 = Ptr{fieldtype(A, 2)}(p_a + fieldoffset(A, 2))
1 Like

How one can create the reference to the field of the instantiated mutable struct?

I am just learning about this stuff, but I think this is impossible. I think the reason is that an isbits value like an Int or a Float64 can be “boxed” and therefore referenced, but an isbits value inside a struct cannot be “boxed”.

I think these are your options:

  • Use a pointer instead of a reference
  • Wrap your isbits value into a mutable struct by using e.g. Ref
  • Use properties to hide this wrapping

As shown in the documentation this could look like (I only implemented getproperty, you can define setproperty! yourself):

mutable struct A
    v1_ref::Ref{Int}
    v2_ref::Ref{Float64}
end

A(v1::Int, v2::Float64) = A(Ref{Int}(v1), Ref{Float64}(v2))

Base.propertynames(::A, private::Bool=false) = private ? (:v1, :v2, :v1_ref, :v2_ref) : (:v1, :v2)

function Base.getproperty(a::A, s::Symbol)
    s === :v1 && return getfield(a, :v1_ref)[]
    s === :v2 && return getfield(a, :v2_ref)[]
    return getfield(a, s)
end

Using it:

a = A(0, 0.0)

julia> a.v1
0

julia> ref_a_v1 = a.v1_ref
Base.RefValue{Int64}(0)

julia> ref_a_v1[] = 4
4

julia> a.v1
4

I’ve defined custom PropertyRefs before like so:

struct PropertyRef{prop, T} <: Ref{T}
    data::T
end
PropertyRef(x::T, prop::Symbol) where {T} = PropertyRef{prop, T}(x)
Base.getindex(x::PropertyRef{prop}) where {prop} = getproperty(x.data, prop)
Base.setindex!(x::PropertyRef{prop}, y) where {prop} = setproperty!(x.data, prop, y)

Here it is in action

julia> a = A(1, 2.0);

julia> x = PropertyRef(a, :v1)
PropertyRef{:v1, A}(A(1, 2.0))

julia> x[]
1

julia> x[] += 1
2

julia> a # mutated!
A(2, 2.0)
5 Likes