How are Arrays of Structs initialized? Am I going about this right?

I’m not sure how to boil this down to one simple question. I have a Struct named LumpOfData with several properties, let’s just make it one named x, and in a class Foo I create an array of these. There is a known fixed number of these, so I allocate an array of that size, ready to be indexed and have values assigned indexed randomly. My LumpOfData has a constructor taking parameters, just one in this case.

struct LumpOfData
    property x
    def initialize(@x : Float32)
    end
end

class Foo
    def initialize(n)
         @lumps = Array(LumpOfData).new(n)
    end

    def add_new_lump(i : Int32,  newlump : LumpOfData)
        if @lumps[i] already has a value 
            throw an exception or whatever
        else
            lumps[i] = newlump
        end
        lumps
    end
end

Elsewhere in this software, I’d be writing code like:

foo.add_new_lump( 45, LumpOfData.new(0.23) )

It’s unclear from documentation (https://crystal-lang.org/reference/ and https://crystal-lang.org/api/0.31.1/index.html mostly) what happens when I allocate the array. Are all the structs initialized with zeros, NaNs, or some default values? (I’m not actually using Float in my code, that’s just for illustration here) How would I tell if some element, say [100], has had a meaningful value assigned or not?

Note I’m coming from C, C++, D and years ago, Pascal (ick!), BASIC (ickier!) and assembly and hands-on electronics, and tend to think differently than those who went through computer science classes. I’m probably thinking about this in a quirky way, trying to write C in Crystal.

You can’t randomly assign array indices. Arrays need to grow from the beginning and there can’t be any undefined values.
Internally, an array is backed by a buffer (invisible to the user). Array(LumpOfData).new(n) does not actually create an array with n elements (which would be uninitialized), it just initializes the buffer with capacity n. The array itself is still empty. A new item can only be appended at the first empty index (0). You can’t randomly assign to any index.

Compared to more primitive arrays (as used in C etc.), this offers more safety because there simply can’t be any uninitialized array items with undefined values.

You can still randomly assign to array indexes, though. In order to do that, you need to grow the array to (at least) the necessary size first. This way, you have to explicitly define a value for “unset” items.
A typical value for this is nil, thus the type of the array would need to change to Array(LumpOfData | Nil) and you can initialize it with Array(LumpOfData).new(n, nil) to an array of nil values.

2 Likes

Type unions are one thing in Crystal I like, but don’t fully ‘get’ yet. If I have an array of LumpOfData | Nil, that might work. Pick a random element early on, for example @lumps[57], get Nil. Otherwise it’s the struct with valid values. Unclear to me: do I want a struct or a class for LumpOfData? I’m thing that for a class, the array is really all pointers, many null and a few pointing to class instances. Maybe I think too much about stack and CPU-level workings - a necessity for those of us working in C, C++ 20+ years ago.

Yes, exactly. Struct values are inlined and class instances are referenced by pointer.
Thus the array bytesize is n * sizeof(LumpOfData) for a struct/value and n * pointer_size for a class.

1 Like