How to use Ptr/Ref?


#1

Suppose I have some big struct and I also have some big dictionary. I want an element in the dictionary to “point to” a field of the struct, such that if I assign to the field of the struct, you’d “see it” by accessing the dictionary, and conversely, if I assign to the element in the dictionary, you’d “see it” in the struct. In C, I’d make the “dictionary” be a pointer to the field, and I could always write to the referenced location. How would this be done cleanly in Julia?

mutable struct MyStruct
    visible
    hidden
end

# Let's create a bunch of things that have both visible and hidden parts.
s = MyStruct(1, 2)

# Let's make a dictionary of visible things.
d = Dict("entry1" => nothing,
         "entry2" => s.visible)

s.visible = 3
display(d["entry2"])  # I'd like to see 3.

d["entry2"] = 4
display(s.visible) # I'd like to see 4.

I could do something cheesy, like make the visible field into a vector with 1 element and write to that, but I don’t like that solution at all.

I’m not sure if this actually involves Ptr or Ref (or pointer). I’m just curious what the right approach is here. It seems like a basic thing to do.


#2

No you can’t do just that. Assigning to a dict can only have one sideeffect-changing the dict-and nothing else.

You can of course store something that can let you find the field in there e.g. the MyStruct itself and just assign to the visible field of that object instead of mutating the Dict. You can also create a wrapper type so that it holds a reference to the MyStruct object and setindex! (or whatever) on it will mutate the visible field.


#3

I had a feeling that this was a question for @yuyichao! Thanks for answering so fast.

It seems like I can use a Ref with the zero-dimensional array “trick” from the Calling C and Fortran documentation to do what I want. I can write to and read from the zero-dimensional with an extra [] on the end.

mutable struct MyStruct{A,B}
    visible::A
    invisible::B
end

s = MyStruct(Ref(1), 2)

d = Dict("entry1" => nothing,
         "entry2" => s.visible)

s.visible[] = 3
display(d["entry2"][]) # Outputs 3

d["entry2"][] = 4
display(s.visible[]) # Outputs 4

This is essentially the same as my “make the visible field into a vector” suggestion, except that the vector is zero-length. In C, you’d need a de-referencing operator (*) to do what I describe, and so we might think of [] as a sort of de-referencing operator here, and hence the solution isn’t more difficult than C. It just looks funny, but so do those asterisks in C.

I’m really asking this question because I don’t understand what a Ref is or how it’s different from a Ptr or a pointer, and there’s very little documentation in this area.


#4

What I was saying that is you don’t need to do this. Having s.visible = 3 to affect what you get from d["entry2"] is totally fine (though d["entry2"] will not be 3). Just that having d["entry2"] = 4 to affect s.visible is not.

You can just make

struct ReferenceOfTheVisibleFieldOfMyStruct
    obj::MyStruct
end
Base.getindex(r::ReferenceOfTheVisibleFieldOfMyStruct) = r.visible
Base.setindex!(r::ReferenceOfTheVisibleFieldOfMyStruct, v) = r.visible = v

and do

d["entry2"] = ReferenceOfTheVisibleFieldOfMyStruct(s)

instead.

Unless you want to convert it to a pointer, it’s just a normal type. There’s nothing special about it which is why there’s no document about it. There’s nothing to document. It is used to represent certain argument convertion in ccall / cfunction and that’s it. A Ptr is, well, a pointer. A Ref can usually be converted to a Ptr (since their use in ccall requires this) using unsafe_convert but that’s not something special about them. (It’s just one of the many object that has this property in Base and it is used to represent a specific type of convertion).

You can simply ignore the

Behaves like a Ptr{T} that can manage its memory via the Julia GC.

In the doc.