Vector, a fixed-size array

Also agree this is a very nice addition, not much to add there (thanks! perhaps? :slight_smile: ) - to add one more possibility to the bike shedding I'd suggest StaticArray (I am also in the camp that thinks Vector is suboptimal).

2 Likes

Note: the pitched type is not static in the sense of StaticString or StaticBigInt.

9 Likes

Wanted to chime in as a person with a degree in mathematics that I personally prefer Vector for the reasons described in the proposal :slight_smile:

However, while I believe that the potential confusion as raised by the people above could be a valid reason to consider a different name, I nevertheless wonder how this would practically manifest itself: since Vector's signature requires an explicit generic Int parameter, I find it actually unlikely that people won't be able to figure out the difference — if one reads

struct Foo {
    var items: Vector<16, Item>
}

I find it hard to believe that people would be expecting a dynamically-sized array here, the number makes it pretty obvious that there's some fixed size. Conversely, seeing Array<Int> or [Int] is generally already enough to imply that there's no fixed length.

21 Likes

This will fix a major pain point in C interop which will be most welcome. Also agree perhaps there’s a better name than Vector.

1 Like

Indeed, I think this is worth emphasizing. In Swift lingo, we've tended to use count to refer to the number of elements in a collection, whereas size tends to refer to the memory layout size.

What's being pitched here is an array-like type where its values have not only fixed size but also fixed count at instantiation.

There may also be a role for an array-like type with only fixed size but not count, but it is not what's been pitched here.

7 Likes

This is a long due addition to the language.

I like the typename Vector, because it is short, and it means something that's familiar from linear algebra.

u: Vector <3, Int>
v: [3, Int]
w: [3 x Int]

u2: Vector <3, Vector <3, Q>>
v2: [3, [3, Q]]
w2: [3 x [3 x Q]]

struct Q {numerator: Int; denominator: Int}

7 Likes

I appreciate the effort and understand the need for a fixed-size collection type, but I have some reservations regarding the proposed approach.

The Motivation section begins by addressing the use of tuples, which is a valid and efficient solution available in Swift today. Tuples are particularly well-suited for this use case due to their static, inlined nature, resulting in a low memory footprint. However, the argument highlights the lack of collection-style APIs for tuples.

It’s worth noting that there have been previous efforts, such as Slava’s pitch on User-defined tuple conformances, aimed at addressing this limitation. Have you considered this as an alternative? While that proposal primarily covers Equatable and Hashable conformances, extending it to make tuples conform to ExpressibleByArrayLiteral, Collection, or MutableCollection could be equally effective in providing the desired functionality.

It’s perfectly valid to prefer a nominal type over a structural tuple for this use case, but I’d like to better understand the trade-offs involved, especially in comparison to a simpler alternative that avoids the complexity of introducing integer generics.

1 Like

It would be difficult to come up with a definition of “mathematical value” that includes all the things mathematicians use vectors for while excluding those things you listed above. Remember, structs and enums are called algebraic data types for a reason ;-)

14 Likes

And in Common Lisp, vector is a subtype of array — arrays can be multi-dimensional in general, and a vector is just a one-dimensional array, which can itself be fixed size (they call that a “simple vector”) or not.

“Vector” doesn’t really mean “growable array”, it’s just something programmers made up. I suspect what happened is those languages that got fixed size arrays first called them “array”, and when the fancy growable thing came along they just picked a random synonym without much thought (because programmers don’t tend to think too much about these sorts of things).

11 Likes

The Motivation section begins by addressing the use of tuples, which is a valid and efficient solution available in Swift today.

I mostly need this for C interop, I need to work with things like uint8_t rom[209715200] for emulators and VMs. Tuples just aren't ergonomic enough, for these sizes they aren't even supported! Even the larger supported sizes just bring the compiler to its knees.

5 Likes

Tuple conformances can only express the special case of a tuple that conforms to a protocol because all of its elements do. This works for Equatable and Hashable but doesn’t make sense for, eg, Collection.

1 Like

Both Stroustrupš and Stepanov² have written and talked about how this is exactly what happened in C++ (and, FWIW, both say that it was likely a mistake).


š "One could argue that valarray should have been called vector because it is a traditional mathematical vector and that vector should have been called array."

² "This was inconsistent with the much older meaning of the term in mathematics and violates Rule 3; this data structure [std::vector] should have been called array." See also https://www.youtube.com/watch?v=etZgaSjzqlU starting around 6:23

18 Likes

Just chiming in here with another large +1 for the feature and a huge -1 for the name. I much prefer FixedArray.

I work a decent amount with geometry, and have come to be very familiar with points, rects, and vectors. The "vector" name proposed here is confusing terminology that will be unfamiliar to the vast majority of people who will ever use it.

10 Likes

Isn’t Vector<3, Double> a vector in the Euclidean geometry sense?

16 Likes

It certainly is if you endow it with a compatible addition and scalar multiplication.

9 Likes

Would it be appropriate for the Swift compiler to implicitly convert from Vector to Unsafe{Mutable}Pointer<Element>, but only when calling into C? I think the alternative — using existing APIs — is withUnsafe{Mutable}Bytes(of:_:) and withMemoryRebound(to:_:) for each vector argument.

Should vectors have explicit conversions to and from tuples? There might be users that need vectors and tuples, when migrating to the upcoming feature (without breaking ABI or source compatibility).

1 Like

Surely, both swapAts needs to be a mutating method, not a borrowing one. I also agree that swapAt(_:with:) really ought to be exchangeAt(_:with:).

+1 for the proposal as a whole, but I'm not 100% sold on the name. Not that it really matters since most of the time I'd be using the [T; N] shorthand (or whichever other shorthand we pick if we decide to be objectively wrong :P)

2 Likes

I support adding this type to the standard library. It'll be useful for many performance-sensitive use-cases and I can't wait to use it :blush:.

I'm confused about how the consume method is meant to work. The documentation says it "will call the closure Count times", but only returns one Result, which doesn't make any sense.

Naming thoughts

This to me is the crux of why I'm not super happy with Vector as a name[1]. Coming from the perspective of graphics/3D rendering, I'd expect a Vector type to have mathmatical operations defined on it and NOT be a collection (basically SIMD types but with generic size).

let a = Vector(1, 2, 3)
let b = Vector(4, 5, 6)

let result = a + b // should be Vector(5, 7, 9)

Additionally, I would love to see a type that is a fixed-count version of the proposed Span type, and a name like RigidArray would scale well to something like RigidSpan.

P.S. I'm curious to know if something like this type would be useful for others. My particular use-case is in serialization, where certain chunks of a file have a size known at compile-time, (e.g. this header is always 24 bytes) and bounds-checking can be omitted at runtime.


  1. I like RigidArray or FixedArray—they better communicate that this is just a special kind of array, and this naming scheme would work well with a potential RigidSpan, InlineArray, or SmallArray ↩︎

5 Likes

Yeah, I prefer to explain this distinction in terms of the value being “stored inline” or not. “On the stack” vs “on the heap” doesn’t capture this properly, and also gets muddy when you have async functions and coroutines and so on, which technically might require heap allocation of stack frames.

Also in the ancient past, local variables of generic type were actually heap allocated, before we switched code generation to use dynamic alloca. While this was suboptimal, it was still only the “outermost” value being allocated out of line, so it was qualitatively different from a representation where all values are boxed.

4 Likes

It would be a different pitch for sure, but there’s certainly a good case that Vector (or FixedArray or whatever) should actually conditionally conform to AdditiveArithmetic (and maybe other protocols) if its element type does, because there is only one possible implementation of this conformance that works.

9 Likes