It’s fine.
Because sometimes you want to change the pointer. But yes, immutables as a wrapper for arrays is a very common “trick”. When you do this, you can make them dispatch and broadcast differently. This kind of wrapper type is actually the suggested implementation for @threads broadcasting.
Immutables are value-types. It’s easiest for me to understand this in contract to mutable types which are not. Mutable types are pointers to each field. So each access has an indirection, and this can ruin optimizations and cause other performance issues (in reality, if it’s not a tight loop, you won’t notice this). Arrays of mutables are arrays of pointers.
Value types have their values “inlined”, so an array of immutables is not an array of pointers, it’s an array of larger pieces of data all written in a line. If an array of immutables concretely typed and you want to grab A[i].b, the compiler can know exactly how many bits to move down the memory to get the value (not just the pointer). So an immutable is almost like a larger primitative type which has a tag in the front to denote what it is and what’s in there.
Arrays are mutable types. So when you put an array in an immutable, the field A.b is a pointer to the array. So there’s one level of indirection. But if A was mutable, it would be two pointers that would have to be dereferenced. But one level of indirection is enough to get rid of many optimizations, so that’s why you won’t notice too much of a difference unless you’re in a tight loop. And in the case where you have immutables, many times the compiler will compile away the “immutable” and turn it into a “zero-cost abstraction”.
I might have some of the details a little bit off, so someone else will probably want to clean this up, but that’s the general idea.