A warm welcome to the community.
I think the problem you do encounter is that
ntuple(Val(length(x))) do i
Base.@_inline_meta
if !(x[i] isa Tuple)
c[]+= f(x[i], rand())
else
c[]+= 1/(foo(x[i]))
end
end
creates a tuple, which is not used, but as it seems not optimized away by the compiler.
You can get what you want by using a different kind of meta-programming magic.
bar(x) = bar(x,Val(length(x)))
@inline @generated function bar(x,::Val{N}) where N
quote
c = 0.0
Base.Cartesian.@nexprs $N i -> c += x[i] isa Tuple ? 1/(bar(x[i],Val(length(x[i])))) : f(x[i],rand())
return c
end
end
yields
a = myStruct1(1)
b = myStruct2(1)
x1 = ((b, (a, b)), (((a, (a,(b,b))), a), b))
@btime bar($x1) # 36.172 ns (0 allocations: 0 bytes)
@btime foo($x1) # 154.228 ns (12 allocations: 432 bytes)
On your side note. If you evaluate foo(x1) once, prior to the benchmark, both benchmarks yield the same result. Actually, this should not happen with BenchmarkTools. I have no idea why it does.