Struct wrapping NamedTuple is not `isbits` (depending on how it is defined)

I am wrapping NamedTuple to define some new/different behaviour without committing type piracy and ran into the following problem:

struct Wrapper{SYMS, TT}
   x::NamedTuple{SYMS, TT}
end
randnt() = Wrapper((a = randn(), b = rand(), c = rand(1:5), d = randn(SVector{3, Float64})))
X = randnt()
@allocated randnt()  # 64
isbits(X)   # false
isbits(X.x)   # true 

This is not good, I really need Wrapper to be non-allocating. Does this have anything to do with the fact that Symbol is not isbits?

Bizarrely, though, (for me anyhow…)

struct Wrapper1{NT} 
   x::NT
end
randnt1() = Wrapper1((a = randn(), b = rand(), c = rand(1:5), d = randn(SVector{3, Float64})))
X1 = randnt1()
@allocated randnt1() # 0 
isbits(X1) # true
isbits(X1.x) # true

I cannot grasp the fundamental different between these two.

To explain why I prefer the first implementation Wrapper: I use quite a few generated functions which need the SYMS information. With a tiny bit of extra code this can of course be done for Wrapper1 as well, so not too big a deal. (well-somewhat, I now have to fix a few 100 lines of code and not introduce bugs). But I am very curious and would like to understand what the difference here is?

3 Likes

Isn’t the named tuple still abstract because the length is not defined?

I don’t think so. The codes all seem type stable.

On my computer (old mac), both the first and second codes give “0 true true” with Julia 1.6.1. So the behavior may depend on versions?

2 Likes

Same here:

julia> VERSION, isbits(X), isbits(X.x)
(v"1.6.1", true, true)
julia> VERSION, isbits(X), isbits(X.x)
(v"1.7.0", false, true)
1 Like

That would explain why I never noticed it until now :slight_smile:

Uh oh. That might be a bug then, I’d recommend opening an issue if there’s not one already.

2 Likes

Will do … EDIT: done

1 Like

Yeah, definitely looks like a bug to me:

julia> nt(::Wrapper{S,T}) where {S,T} = NamedTuple{S,T}
nt (generic function with 1 method)

julia> nt(X)
NamedTuple{(:a, :b, :c, :d), Tuple{Float64, Float64, Int64, SVector{3, Float64}}}

julia> nt(X) === typeof(X.x)
true

julia> isbits(X)   # false
false

julia> isbits(X.x)   # true
true

The wrapper seems to be matching the NamedTuple exactly.

2 Likes