What if we want the ability to manipulate several values of the same type, all stored together sequentially in memory? Cone offers these safe, built-in types to support this capability:

Other indexed collection types (e.g., vectors and maps) use these built-in types (or raw pointers) as a foundation.

Declaration and Creation

As a quick glance, these examples show the type signatures for the built-in array types listed above:

imm staticarray [10] f32      // Static array
imm arrref      &own [] f32   // Dynamic array (allocated array reference)
imm slice       &[] f32       // Slice (borrowed array reference)

Note that dynamic arrays and slices are in fact a special kind of reference: an array reference. Dynamic arrays are array references managed by an allocator. Slices are borrowed array references.

Static Arrays

A static array is a fixed-size collection of elements whose size is known at compile-time. It can exist as a global variable or allocated on the stack like any other local variable. The type signature for a static array specifies the number of elements as an unsigned integer literal inside square brackets, followed by the type of every element:

mut text [4] u32

A static array is initialized using an array constructor, a comma-separated enumeration of same-typed values enclosed within square brackets:

mut list = [1f, 4f, 5f]   // [3] f32

As one would expect, a global variable declaration requires the array constructor to contain only literal values. All other uses of the array constructor allow the array values to be expressions.

A static array may also be initialized using a string literal:

imm hellotext = "Hello!"  // [6] u8

Dynamic Array

A dynamic array is a dynamically-sized collection of elements allocated on the heap at runtime. A dynamic array is represented by an array reference, which is effectively a tuple containing:

Allocation of a dynamic array looks similar to simple allocations, returning an array reference as a result. There are several different ways to allocate a dynamic array:

Resizing

Resizing is only possible for an &own-based dynamic array (array reference). This restriction ensures memory safety, protecting against invalidating other references as a result of altering the size or position of a dynamic reference.

Resizing uses the resize command with similar format as above:

Slices

A slice is a borrowed array reference providing direct access to a subset of elements stored in some array. It is created by applying an index range to an existing array or slice via the ampersand (&) operator. The range specifies two unsigned integers within square brackets, separated using the '..', '...' or 'by' operator. '..' excludes the last specified element, whereas '...' includes it. The second number on 'by' specifies how many elements are in the slice.

imm x = &"Abcd"[1..3]    // a slice pointing to "bc"
imm y = &"Abcd"[1...3]   // a slice pointing to "bcd"
imm z = &"Abcd"[2 by 1]  // a slice pointing to "c"

If either index integer is unspecified, 0 and end are assumed.

imm r = &y[1..]        // a slice pointing to "cd" (end assumed)
imm s = &y[..]         // a slice pointing to "bcd" (0 and end assumed)

A slice may also be created from a normal reference or a pointer:

imm slice1 = &ref[..]      // Slice count from reference is always 1
imm slice2 = &ptr[0...5]

And, finally, If a static array is passed as a call argument where a slice is expected, it is automatically converted into a slice.

Safety Note: Because a slice is a borrowed reference, its lifetime is limited, ensuring it can never outlive the source array it provides a view into. Depending on permissions, it may temporarily lock out access to the variable it borrows from, thereby ensuring the original array cannot be moved or restructured while the slice is active. A slice may not be created from an array that has the shared, mutable permission 'mut', as this could lead to potential memory safety problems.

Array Operations

Arrays may be indexed, copied, compared, iterated over, resized, and queried for their properties. These operations work the same way on static arrays, dynamic array, and slices, except where otherwise noted.

Indexing

A single element within an array may be accessed using an unsigned integer value enclosed in square brackets. This supports both getting and setting the values at a specific index:

c_array[3] = c_array[2]    // the 4th element is now the same as the third

Array indexing is automatically bounds-checked by its known size. This technique works with pointers as well, but never performs any bounds-check. Using a pointer this way can improve performance where we know the index is safe.

A borrowed reference to a single element can be obtained using the ampersand (&) operator:

imm borref = &sliceref[4]   // borrow reference to 5th element

Multiple elements of an array may be extracted and copied into another array using a range in square brackets. Both content segments must have the same type and size.

arrref[1 by 2] = sliceref[0 by 2]
greetings[n by 5] = "Kevin"

Similarly, a single value can be repeatedly filled into a slice's elements:

arrref[1 .. 3] = 2

Iteration

The each block provides an easy way to iterate over a static array, dynamic array, or slice:

mut sum = 0
each x in intslice
    sum += x

Iteration can return a borrowed reference instead of the value:

each &mut x in intslice
    ++x

Iteration can also show the index number for each value:

each i, x in intslice
    newslice[i] = x

Comparison

Two array references may be compared for equality, so long as they have the same element type, even if one is a borrowed reference and the other an allocated reference. This comparison checks for equality of both the starting pointer and the element count.

if ownref == borref
   ....

Comparing for order (e.g., <) is not possible directly. One can of course compare the component parts of an array reference separately (see below).

Use iteration to compare the contents in two arrays.

Property Queries

The component parts of an array or array reference can be retrieved separately:

imm ptr = array.ptr     // The pointer to the first element
imm len = array.len     // The number of elements
imm count = array.count // The number of elements
imm size = array.size   // The memory size used

Although .count and .len returns the same value for these types, this is not necessarily true for other types, where .len represents the capacity and .count represents how many elements contain a stored value.

Structs as indexed collections

More complex collections may be created using the built-in array types as a foundation. This is typically accomplish by defining a struct with a dynamic array as one of the fields. This custom collection can handle all of the operations described earlier because these capabilities are invoked as methods on the struct object. The struct's methods can then forward these requests down to the dynamic array as appropriate.

These are the implicit names for these methods:

each iteration

TBD.

Partial match

TBD

_