Search code examples
arraysd

How to create an array that has a set size but unset values?


How do I create an array, which has a set size, that is not known at compile time, but has unset values?

Essentially I want something like int immutable([length]);. length is not known at compile time. Obviously that doesn't compile though.


Solution

  • It'd have to be user-defined. The built-in arrays in D are either static, needed to be known at compile time, or slices into a dynamic array, which can be resized.

    Built-in options:

    int length = 100;
    int[] int_array = new int[length]; // works, contents will be initialized to zero
    

    That's the same as:

    int[] int_array;
    int_array.length = length;
    

    You can also do immutable(int)[] if you want that, though then you won't be able to set the contents... the normal way to do this would be to write a pure function that creates and sets contents in a mutable array, then returns it:

    pure int[] make_array(int length) {
        int[] array;
        array.length = length;
        foreach(i, ref item; array)
                item = i; // initialize it to a simple count
        return array;
    }
    
    // usage:
    immutable(int)[] arr = make_array(100); // works
    immutable(int[]) iarr = make_array(100); // also works
    

    Mutable data returned from a pure function is the exception to the general prohibition of implicit casting to imuutable: since it comes from a pure function, the compiler knows it is a unique reference, and safe to treat as immutable upon request.

    The difference between the first and second line of usage is the first one can be reassigned: arr = something_else[]; /* cool */ whereas the second one cannot be changed at all. No length change, no contents change, no reassignment.

    Static arrays are an option, but the length needs to be known at compile time:

    int[100] int_array = void; // works, contents uninitialized, non-resizable, but length must be known at compile time
    

    One possible strategy there is to declare a big int_array_buffer, then set int_array = int_array_buffer[0 .. length]. Though, still, int_array will be resizable itself.

    To get everything you want, it'll have to be a user-defined type. Something alone these lines could work:

    struct MyArray(T) {
         @disable this(); // disallow default construction; force a length
         // this constructor takes a runtime length and allocates the backing
         this(size_t length) { backing = new T[length]; }
         private T[] backing = void; // actually holds the data, uninitialized
         T[] opSlice() { return backing; } // allow easy conversion to a slice
         alias opSlice this; // implicit conversion to slice
    }
    

    When passing it to a function, you can pass MyArray!int, or a plain int[]. Thanks to the alias this, it will implicitly convert to int[], and thanks to the D slicing rules, even if the slice is resized in that function, it won't affect your MyArray instance.

    Let's look at some usage:

    void main() {
        int length = 100; // runtime
        // MyArray!int uninitialized; // compile error thanks to @disable this
        MyArray!int uninitialized = void; // ok, explicitly uninitialized
        uninitialized = MyArray!int(length); // created
    
        // or
        auto my_array = MyArray!int(length); // also creates it
    
        // my_array.length = 20; // compile error "my_array.opSlice().length is not an lvalue" - it is not resizable
        // my_array ~= 1; // compile again, "cannot append type int to type MyArray!int" - again because it is not resizable
    
        int[] slice = my_array; // works, makes passing it to functions that expect normal arrays easy
    }
    

    That should give you everything you need. With wrapper structs in D, you can selectively enable and disable functionality of the underlying type with no loss in efficiency.