I think it’s fairly easy to come up with composable Ref based on the idea @vchuravy mentioned:
using Base: fieldindex, unsafe_convert
struct RefField2{f,T,R <: Ref} <: Ref{T}
x::R
end
function RefField2(r::Ref{T}, f::Int) where {T}
@assert isbitstype(fieldtype(T, f))
return RefField2{f, fieldtype(T, f), typeof(r)}(r)
end
RefField2(r::Ref, name::Symbol) = RefField2(r, fieldindex(_reftype(r), name))
_inner(r::RefField2) = getfield(r, :x)
_reftype(::Ref{T}) where {T} = T
function Base.unsafe_convert(::Type{Ptr{T}}, r::RefField2{f, T}) where {f, T}
iref = _inner(r)
p::Ptr{T} = unsafe_convert(Ptr{_reftype(iref)}, iref)
return p + fieldoffset(_reftype(iref), f)
end
Base.pointer(r::RefField2) = unsafe_convert(Ptr{_reftype(r)}, r)
Base.getproperty(r::RefField2, name::Symbol) = RefField2(r, name)
Base.getindex(r::RefField2) = unsafe_load(pointer(r))
Base.setindex!(r::RefField2, x) = unsafe_store!(pointer(r), convert(_reftype(r), x))
struct RefArray2{T,A<:Array{T}} <: Ref{T}
x::A
i::Int
@inline function RefArray2(A::Array, i::Int)
@boundscheck checkbounds(A, i)
return new{eltype(A),typeof(A)}(A, i)
end
end
RefArray2(A::Array, I) = RefArray2(A, Base.to_index(A, I))
_array(r) = getfield(r, :x)
_index(r) = getfield(r, :i)
Base.unsafe_convert(::Type{Ptr{T}}, r::RefArray2{T}) where {T} =
pointer(_array(r), _index(r))
Base.pointer(r::RefArray2) = unsafe_convert(Ptr{_reftype(r)}, r)
Base.getproperty(r::RefArray2, name::Symbol) = RefField2(r, name)
Base.getindex(r::RefArray2) = unsafe_load(pointer(r))
Then
julia> xs = [(a=(b=(c=x, d=2x), e=3x), f=4x) for x in 1:2]
2-element Array{NamedTuple{(:a, :f),Tuple{NamedTuple{(:b, :e),Tuple{NamedTuple{(:c, :d),Tuple{Int64,Int64}},Int64}},Int64}},1}:
(a = (b = (c = 1, d = 2), e = 3), f = 4)
(a = (b = (c = 2, d = 4), e = 6), f = 8)
julia> r = RefArray2(xs, 2).a.b.c; # reference to `xs[2].a.b.c`
julia> r[]
2
julia> r[] = 2000;
julia> xs
2-element Array{NamedTuple{(:a, :f),Tuple{NamedTuple{(:b, :e),Tuple{NamedTuple{(:c, :d),Tuple{Int64,Int64}},Int64}},Int64}},1}:
(a = (b = (c = 1, d = 2), e = 3), f = 4)
(a = (b = (c = 2000, d = 4), e = 6), f = 8)
Though it’s not as efficient as I hoped. For example, looking at the code generated for r[]
, not all unsafe_convert
calls are inlined.