3 ms·
> Arrays of ambiguous size were a really bad idea. When? And for whom? Having to make everything a fixed size array like in C is no good either, that's for sur
by bruce343434 5y ago
> Arrays of ambiguous size were a really bad idea.
When? And for whom? Having to make everything a fixed size array like in C is no good either, that's for sure. It leads to bad guesses and comments like "should be big enough".
- vlovich123 5y agoI think it’s more like a resizable array class that keeps track of the size for you.
- vlmutolo 5y agoI think he was referring to areas of contiguous memory (arrays) where the size of the area is separated from the pointer to that area. Nearly any operation on the array will require both pieces of information, and tons of C bugs come from making assumptions about the length of an array that aren't true. So, better just to carry the length along with the pointer (Rust calls these "fat" pointers) and use that as the source of truth about the array length (for dynamically sized arrays, such as those created by malloc).
- LegionMammal978 5y agoThey're referring to dynamically-sized arrays that do not store their own length. In C, this would be a pointer to the first element of an array; the length of the array must be passed around independently to use it safely. Instead, they're advocating collections which store their own length, such as vectors in C++ or arrays in C# or Java. Personally, I believe there is a need for three different kinds of array types: 1. Array types of fixed length, ensured by the type system. This corresponds to arrays in C, C++, and Rust, and fixed-size buffers in C#. 2. Array types of dynamic length, in which the length is immutable after creation. This corresponds to arrays in C# and Java, and boxed slices in Rust. 3. Array types of dynamic length which can be resized at runtime. This corresponds to vectors in C++ and Rust, and lists in C#, Java, and just about every interpreted language. Of course, for FFI purposes, it is often acceptable to pass arrays as a simple pointer + length pair, this being the common denominator of sequential arrays across low-level languages.
- brandmeyer 5y agoWe also frequently need reference types to contiguous storage in addition to value types of contiguous storage. In C++ this is satisfied by std::span for both the static and dynamic cases.
- LegionMammal978 5y agoTrue. In Rust those are regular slices, and in C# and Java all collections are owned by the runtime anyway. Something which several languages do lack, though, is a view of a contiguous subsequence, in the manner of an std::span; it would be nice to see those in more places. I've also taken a look at Golang's slices, which have rather confusing ownership semantics. One can take a subslice of a slice, and its elements will track those of the original, but appending to that subslice will copy the values into an entirely new array. In fact, appending to the original slice past its capacity causes a reallocation, which can invalidate any preexisting subslices. This also occurs with C++ vectors and spans, if I am not mistaken. This is an area where I think Rust's borrow checker really shines; it prevents you from resizing a vector if there are any active slices, encouraging you to instead store a pair of indices or some other self-contained representation.
- brandmeyer 5y agoYou cannot append to a std::span, or append to a vector-backed span through the span. You have to perform the append on the underlying vector. It is possible to perform an insertion into the middle of a std::vector through std::vector::insert. If you can establish the precondition that the underlying vector will not re-allocate as a result of the append, then it is perfectly safe to perform such an append while holding a reference to one or more elements of the vector. Same thing for insertions: references to elements before the insertion point may still remain valid if you can establish the precondition that the vector will not be resized. In both cases, it is straightforward to establish the precondition through the reserve() member plus some foreknowledge of how much extra capacity the algorithm needs. You can always construct a user-defined reference type which back-doors the borrow checker, such as by storing indexes instead of iterators as you mentioned. If the std.vector is reduced, then they are still just as invalid.