# How to make a struct that contains a reference to other memory?

I have a vector of nodes, something like

``````struct Node
x::Float64
# more fields
end
nodes = Vector{Node}(undef, bignumber)
``````

And I have a vector of Elements that each contain some nodes

``````struct Element
numNodes::UInt8
tag::Int
nodes::Vector{Node}
# more fields
end
elems = [Element(n, i, nodes[inds]) for inds in indices]
``````

This is terrible for size reasons, so my first thought is to use Ref since the manual makes it seem like the Julia version of pointers.

``````struct Element
numNodes::UInt8
tag::Int
nodes::Vector{Ref{Node}}
# more fields
end
elems = [Element(n, i, Ref.(nodes[inds])) for inds in indices]
``````

But this doesnâ€™t work, the only thing that seems to work is using SubArrays/Views

``````struct Element
numNodes::UInt8
tag::Int
nodes::SubArray
# more fields
end
elems = [Element(4,i,@view nodes[inds] #= etc =#) for inds in indices]
``````

My question is then two fold:
Is this the correct way of doing this or is there a better more performant way to do and think about this in Julia?
How do you make a struct that contains a reference to another bit of memory? Since using a view would seem silly for something that isnâ€™t an array.

P.S. Is a Ref not actually the Julia version of a Pointer?

Hi there!
Can you give a complete MWE, and explain why the solutions you have â€śdonâ€™t workâ€ť or are â€śterribleâ€ť? In particular, it seems `indices` is a vector of vectors?

2 Likes

MWE of Ref not working

``````julia> struct Foo
y::Float64
end

julia> struct Bar
l::Int
o::Vector{Ref{Foo}}
end

julia> foos = [Foo(i) for i in 1:4]
4-element Vector{Foo}:
Foo(1.0)
Foo(2.0)
Foo(3.0)
Foo(4.0)

julia> bar = Bar(2, Ref.(foos[1:2]))
Bar(2, Ref{Foo}[Base.RefValue{Foo}(Foo(1.0)), Base.RefValue{Foo}(Foo(2.0))])

julia> foos[1] = Foo(5)
Foo(5.0)

julia> bar
Bar(2, Ref{Foo}[Base.RefValue{Foo}(Foo(1.0)), Base.RefValue{Foo}(Foo(2.0))])
# Foo(1.0) has not changed
``````

The reason

``````struct Element
numNodes::UInt8
tag::Int
nodes::Vector{Node}
# more fields
end
``````

is terrible is because in the case that you have many `Element`s referencing the same nodes you are copying the data of those nodes, thus this is a waste of memory.

Each `Element` could reference an arbitrary subset of `nodes` which is why `indices` is a vector of the indices of the nodes that each `Element` has.

My goal is to minimize memory usage since I could be making hundreds of thousands of `Node`s, each of which must belong to at least one `Element`.

From reading the manual it seems like one should be able to use `Ref` to reference another piece of memory like pointers do in C, am I mistaken?

I apologize for not giving a more thorough explanation.

1 Like

`foo[1:2]` makes a copy of the elements.
What you want to use here is `bar = Bar(2, [ Ref(foos, i) for i in 1:2 ])`. See the doc string of `Ref`.

Also note that

``````julia> isabstracttype(Ref{Foo})
true
``````

which will make iterating over this vector slow.
You should instead use `Vector{Base.RefValue{Foo}}`.

Yes, they can be used that way.

But if you are dealing with arrays, I would recommend you to work with `view` and `SubArray`s instead.

E.g.

``````julia> struct Bar{V<:AbstractVector{<:Foo}}
l::Int
o::V
end

julia> bar = Bar(2, view(foos, 1:2))
``````
4 Likes

You could make `Node` be a `mutable struct`. This way, putting some of them in an array doesnâ€™t copy the content around, just a pointer.

Another solution is to use views. You already suggested this in your OP but didnâ€™t really say why thatâ€™s a bad idea. Seems reasonable to me.

2 Likes

Using views does make sense, though the context in how Iâ€™ve seen views used is very different from how Iâ€™ve seen pointers used on other languages (i.e. a way to avoid copying, rather than a data type in and of themselves).
And I was wondering if there was a convention I was missing.

My main point of confusion was how different the syntax is when pointing to an array vs not an array (as shown by @fatteneder ). And I didnâ€™t know that broadcasting made copies of the arguments.

Not only broadcasting makes a copy.
In the example above already slicing the array with `foos[1:2]` makes a copy. And then calling `Ref.(foos[1:2])` makes a second copy.

1 Like

Just one more question:
Why `V<:AbstractVector{<:Foo}` and not `V<:SubArray`?
Do these tell the compiler substantiality different things or is this a human readability choice?

Its more general.
If you were to restrict to `V<:SubArray` you couldnâ€™t call

``````julia> bar = Bar(2, foo)
``````
1 Like

There is almost never a performance benefit to tightening constraints on type parameters (unless maybe the constraint is a concrete type, but then it should not be a parameter). They are mostly just there for for their ability to add convenient guardrails against stuff going too crazy. They should mostly be unconstrained or constrained to broad, abstract types.

In your case, maybe thereâ€™s some world where a `NTuple{3,Foo}` would be reasonable in that parameter. Though this is not `<:AbstractVector{Foo}`, it meets most of the interface for it and would probably work for most things. Thereâ€™s no cost to leaving this as a hypothetical possibility, so itâ€™s nice to have it.

At runtime, you will have a `Bar{SubArray{<complete type parameterization to make a concrete type>)}}.` This is what the compiler will actually specialize for. Since it specializes at that level, there isnâ€™t real benefit to a tighter a-priori constraint.

1 Like

That brings up another thing I tried for the same project as this.

Is there a way to make something like

``````julia> struct Node
dim::UInt8
tag::UInt
pos::NTuple{dim, Float64}
end
ERROR: UndefVarError: `dim` not defined
``````

Where the size of the `NTuple` is a parameter, like `Node{Dim}`? Or must one make many structs like

``````Node1D
Node2D
Node3D
...
``````

And this would be to make the struct only use concrete types.

Yes. EDIT: I wasnâ€™t reading closely so originally left `dim` as a field.

``````struct Node{dim}
tag::UInt
pos::NTuple{dim, Float64}
end
``````
``````julia> fieldtype(Node{0}, :pos)
Tuple{}

julia> fieldtype(Node{1}, :pos)
Tuple{Float64}

julia> fieldtype(Node{2}, :pos)
Tuple{Float64, Float64}

julia> fieldtype(Node{3}, :pos)
Tuple{Float64, Float64, Float64}
``````
1 Like

But drop `dim` as a field in the struct. It should just be a type parameter. If Iâ€™m not mistaken, the `dim` field is now unconnected with the parameter, and also wholly redundant.

``````julia> x = Node{3}(2, 10, (1.0, 2.1, 3.2));

julia> x.dim
0x02
``````
2 Likes