You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Update InlineArray sugar proposal from x to of (#2861)
* Update 0483-inline-array-sugar.md
Switch from `x` to `of` as the separator. Introduce an alternatives considered option that covers the naming of `InlineArray`.
* Update 0483-inline-array-sugar.md
* Update 0483-inline-array-sugar.md
* Update 0483-inline-array-sugar.md
* Update 0483-inline-array-sugar.md
* Fix typos discovered by @ole and @benrimmington.
* Minor editorial changes.
* Update the review status in preparation for revision review.
---------
Co-authored-by: Holly Borla <[email protected]>
Declaring this type is more cumbersome than its equivalent dynamically-sized array, which has sugar for the type syntax:
23
23
24
-
```
24
+
```swift
25
25
let fiveIntegers: [Int] = .init(repeating: 99, count: 5)
26
26
```
27
27
28
28
This becomes more pronounced when dealing with multiple dimensions:
29
29
30
-
```
30
+
```swift
31
31
let fiveByFive: InlineArray<5, InlineArray<5, Int>> = .init(repeating: .init(repeating: 99))
32
32
```
33
33
34
+
Almost every other language in a similar category to Swift – C, C++, Objective-C, Pascal, Go, Rust, Zig, Java, C# – has a simple syntax for their fixed-size array type. The introduction of a fixed-size array type into Swift should also introduce a shorthand syntax, in keeping with Swift's general approach of low ceremony and concise syntax. Swift further deviates from its peer languages by giving its _dynamic_ array type, `Array` (known in many other languages as `vector`) a sugared form. This can lead to an assumption that `Array` should be used under almost all circumstances, despite it having significant downsides in many uses (see further discussion in alternatives considered).
35
+
34
36
## Proposed solution
35
37
36
38
A new sugared version of the `InlineArray` type is proposed:
37
39
38
40
```swift
39
-
let fiveIntegers: [5 xInt] = .init(repeating: 99)
41
+
let fiveIntegers: [5 ofInt] = .init(repeating: 99)
40
42
```
41
43
42
-
The `x` here is the ASCII character, and is chosen to evoke the common shorthand use to represent "by", as in "4x4" or "2 in x 4 in".
43
-
44
-
Note that although it is used in the manner of an operator, `x` here serves more like a contextual keyword, similar to if the syntax were `[5 of Int]`.
44
+
The choice of `of` forms something close to a grammatical phrase ("an array of five ints"). A short contextual keyword is also in keeping with Swift's tradition in other areas such as `in` or `let`.
45
45
46
46
## Detailed design
47
47
48
-
The new syntax consists of the value for the integer generic parameter and the type of the element generic parameter, separated by `x`.
48
+
The new syntax consists of the value for the integer generic parameter and the type of the element generic parameter, separated by `of`.
49
49
50
50
This will be added to the grammar alongside the current type sugar:
51
51
52
52
> **Grammar of a type**
53
53
> _type → sized-array-type_
54
54
>
55
55
> **Grammar of a sized array type**
56
-
> _sized-array-type → [ expression `x` type ]_
56
+
> _sized-array-type → [ expression `of` type ]_
57
57
58
-
Note that while the grammar allows for any expression, this is currently limited to only integer literals.
58
+
Note that while the grammar allows for any expression, this is currently limited to only integer literals or integer type parameters, as required by the current implementation of `InlineArray`. If that restriction changes, so would the value allowed in the expression in the sugar.
59
59
60
60
The new sugar is equivalent to declaring a type of `InlineArray`, so all rules that can be applied to the generic placeholders for the unsugared version also apply to the sugared version:
61
61
62
-
```
62
+
```swift
63
63
// Nesting
64
64
let fiveByFive: InlineArray<5, InlineArray<5, Int>> = .init(repeating: .init(repeating: 99))
65
-
let fiveByFive: [5 x [5 x Int]] = .init(repeating: .init(repeating: 99))
65
+
let fiveByFive: [5 of [5 ofInt]] = .init(repeating: .init(repeating: 99))
66
66
67
67
// Inference from context:
68
-
let fiveIntegers: [5 x _] = .init(repeating: 99)
69
-
let fourBytes: [_ x Int8] = [1,2,3,4]
70
-
let fourIntegers: [_ x _] = [1,2,3,4]
68
+
let fiveIntegers: [5 of _] = .init(repeating: 99)
69
+
let fourBytes: [_ ofInt8] = [1,2,3,4]
70
+
let fourIntegers: [_ of _] = [1,2,3,4]
71
71
72
72
// use on rhs
73
-
let fiveDoubles = [5 x _](repeating: 1.23)
73
+
let fiveDoubles = [5of_](repeating: 1.23)
74
74
```
75
75
76
76
The sugar can also be used in place of the unsugared type wherever it might appear:
77
77
78
-
```
79
-
[5 x Int](repeating: 99)
80
-
MemoryLayout<[5 x Int]>.size
81
-
unsafeBitCast((1,2,3), to: [3 x Int].self)
78
+
```swift
79
+
[5ofInt](repeating: 99)
80
+
MemoryLayout<[5ofInt]>.size
81
+
unsafeBitCast((1,2,3), to: [3ofInt].self)
82
82
```
83
83
84
-
There must be whitespace on either side of the separator i.e. you cannot write `[5x Int]`. There are no requirements to balance whitespace,`[5 x Int]` is permitted. A new line can appear after the `x` but not before it, as while this is not ambiguous, this aids with the parser recovery logic, leading to better syntax error diagnostics.
84
+
There must be whitespace on either side of the separator; i.e., you cannot write `[5of Int]`. There are no requirements to balance whitespace;`[5 of Int]` is permitted. A new line can appear after the `of` but not before it, as while this is not ambiguous, this aids with the parser recovery logic, leading to better syntax error diagnostics.
85
85
86
86
## Source Compatibility
87
87
88
88
Since it is not currently possible to write any form of the proposed syntax in Swift today, this proposal does not alter the meaning of any existing code.
89
89
90
90
## Impact on ABI
91
91
92
-
This is purely compile-time sugar for the existing type. It is resolved at compile time, and does not appear in the ABI nor rely on any version of the runtime.
92
+
This is purely compile-time sugar for the existing type. It is resolved at compile time and does not appear in the ABI nor rely on any version of the runtime.
93
93
94
94
## Future Directions
95
95
96
96
### Repeated value equivalent
97
97
98
-
Analogous to arrays, there is an equivalent _value_ sugar for literals of a specific size:
98
+
Analogous to arrays, there is an equivalent *value* sugar for literals of a specific size:
99
99
100
-
```
101
-
// type inferred to be [5 x Int]
102
-
let fiveInts = [5 x 99]
103
-
// type inferred to be [5 x [5 x Int]]
104
-
let fiveByFive = [5 x [5 x 99]]
100
+
```swift
101
+
// type inferred to be [5 of Int]
102
+
let fiveInts = [5 of 99]
103
+
104
+
// type inferred to be [5 of [5 of Int]]
105
+
let fiveByFive = [5 of [5 of 99]]
105
106
```
106
107
107
108
Unlike the sugar for the type, this would also have applicability for existing types:
108
109
109
-
```
110
+
```swift
110
111
// equivalent to .init(repeating: 99, count: 5)
111
-
let dynamic: [Int] = [5 x 99]
112
+
let dynamic: [Int] = [5of99]
112
113
```
113
114
114
115
This is a much bigger design space, potentially requiring a new expressible-by-literal protocol and a way to map the literal to an initializer. As such, it is left for a future proposal.
115
116
117
+
However, the choice of syntax for the type sugar has a significant impact on the viability of this future direction (see alternatives considered). Given the potential benefit of such a value syntax, any choice for the type sugar should consider its future extension to value sugar.
118
+
119
+
[^expressions]: It could also cause confusion once expressions are allowed for declaring an `InlineArray` i.e. `[5 * 5 * Int]` would be allowed.
120
+
116
121
### Flattened multi-dimensional arrays
117
122
118
-
For multi-dimensional arrays, `[5 x [5 x Int]]` could be flattened to `[5 x 5 x Int]` without any additional parsing issues. This could be an alternative considered, but is in future directions as it could also be introduced as sugar for the former case at a later date.
123
+
For multi-dimensional arrays, `[5 of [5 of Int]]` could be flattened to `[5 of 5 of Int]` without any additional parsing issues. This could be an alternative considered but is in future directions as it could also be introduced as sugar for the former case at a later date.
119
124
120
125
## Alternatives Considered
121
126
127
+
### Indication of the "inline" nature via the sugar.
128
+
129
+
The naming of `InlineArray` incorporates important information about the nature of the type – that it includes its values inline rather than indirectly via a pointer. This name was chosen over other alternatives such as `FixedSizeArray` because the "inline-ness" was considered the more fundamental property, and so a better driver for the name.
130
+
131
+
This has led to suggestions that this inline nature is important to include in the sugar was well. However, the current state privileges the position of `Array` as the only array type that is sugared. This implies that `Array` is the right choice in all circumstances, with inline arrays being a rare micro-optimization. This is not the case.
132
+
133
+
For example, consider a translation of this code from the popular [Ray Tracing in One Weekend](https://raytracing.github.io/books/RayTracingInOneWeekend.html#thevec3class) tutorial:
134
+
135
+
```cpp
136
+
classvec3 {
137
+
public:
138
+
double e[3];
139
+
140
+
double x() const { return e[0]; }
141
+
doubley() const { return e[1]; }
142
+
double z() const { return e[2]; }
143
+
// etc
144
+
}
145
+
```
146
+
147
+
The way in which Swift privileges `[Double]` with sugar strongly implies you should use that in this translation. This would be the wrong choice. Every access to those coordinate accessors would need to check the bounds (because while the author might ensure that the value of `e` will only ever have length 3, the compiler cannot easily know this) and, in the case of mutation, a check for uniqueness of the pointer. It would also make `vec3` a nontrivial type, which has significant performance implications wherever it is used. `InlineArray<3, Double>` has none of these problems.
148
+
149
+
Other examples include using nested `Array` types i.e. using `[[Double]]` to represent a matrix – which would also have significant negative consequences depending on the use case, compared to using an `InlineArray` to model the same values. In other cases, the "copy on write" nature of `Array` can mislead users into thinking that copies are risk-free, when actually copying an array can lead to "defeating" copy on write in subtle ways that can cause difficult-to-hunt-down performance issues. In all these cases, you need to pick the right one of two options for the performance goals you are trying to achieve.
150
+
151
+
It is likely that Swift's choice (deviating from many of its peers) to emphasize its dynamic array through sugar, has led to a negative impact on the culture of writing performant Swift code, with the nicely sugared dynamic arrays (and array value literals) being over-favored. This is not intended to make the case that Swift should _not_ have this sugar. Dynamic arrays are widely useful and Swift's readability goals are improved by Swift having a concise syntax for creating them. But writing performant Swift code inevitably involves having an understanding of the underlying performance characteristics of _all_ the types you are using, and syntax or type naming alone cannot solve this.
152
+
153
+
Of course, all this only matters when you are trying to write code that maximizes performance. But that is a really important use case for Swift. The goal for Swift is a language that is as safe and enjoyable to write as many high-level non-performant languages, but also can achieve peak performance when that is your goal. And the idea is that when you are targeting that level of performance, you don't have to go into "ugly, no longer nice swift" mode to do it, with nice sugared `[Double]` replaced with less pleasant full type name of `InlineArray` – something a user coming from Go or C++ or Rust might find a downgrade. Similarly, attempting to incorporate the word "inline" into the sugar e.g. `[5 inline Int]` creates a worst of both worlds solution that many would find offputting to use, without solving the fundamental issue.
154
+
155
+
For these reasons, we should be considering `InlineArray` a peer of `Array` (even if the need for it is less common – just not "niche"), and providing a pleasant to use sugar for both types.
156
+
122
157
### Choice of delimiter
123
158
124
159
The most obvious alternative here is the choice of separator. Other options include:
125
160
161
+
-`[5 by Int]` is similar to `of`, but is less applicable to the value syntax (`[5 by 5]` doesn't read as an array of 5 instances of 5), without being clearer for the type syntax.
162
+
-`[5 x Int]`, using the ascii letter `x`, as an approximation for multiplication, reflecting common uses such as "a 4x4 vehicle". This was the choice of a previous revision of this proposal.
126
163
-`[5 * Int]`, using the standard ASCII symbol for multiplication.
127
164
-`[5 ⨉ Int]`, the Unicode n-ary times operator. This looks nice but is impractical as not keyboard-accessible.
128
-
-`[5; Int]` is what Rust uses, but appears to have little association with "times" or "many". Similarly other arbitrary punctuation e.g. `,` or `/` or `#`.
129
-
-`[5 of Int]`is more verbose than `x` but could be considered more clear. It has the upside or downside, depending on your preference, of being almost, but not quite, grammatical.
130
-
-`:` is of course ruled out as it is used for dictionary literals.
165
+
-`[5; Int]` is what Rust uses, but appears to have little association with "times" or "many". Similarly other arbitrary punctuation e.g. `,` or `/`. `:` is of course ruled out as it is used for dictionary literals.
166
+
-`#` does have an association with counts in some areas such as set theory, but is used as a prefix operator rather than infix i.e. `[#5 Int]`. This is less expected than the infix form, and could also be read as "the fifth `Int`". It is also unclear how this would work with expressions like an array of size `5*5`.
167
+
-No delimiter at all i.e. `[5 Int]`. While this might be made to parse, the lack of any separator is found unsettling by some users and is less visually clear, especially once expressions are allowed instead of the `5`.
131
168
132
-
Note that `*` is an existing operator, and may lead to ambiguity in future when expressions can be used to determine the size: `[5 * N * Int]`. `x` is clearer in this case: `[5 * N x Int]`. It also avoids parsing ambiguity, as the grammar does not allow two identifiers in succession. But it would be less clear if `x` also appeared as an identifier: `[5 * x x Int]`(which is not yet permitted but may be in future use cases).
169
+
Note that `*` is an existing operator, and may lead to ambiguity in future when expressions can be used to determine the size: `[5 * N * Int]`. `of` is clearer in this case: `[5 * N of Int]`. It also avoids parsing ambiguity, as the grammar does not allow two identifiers in succession. This becomes more important if the future direction of a value equivalent is pursued. `[2 * 2 * 2]`could be interpreted as `[2, 2, 2, 2]`, `[4, 4,]`, or `[8]`.
133
170
134
-
This becomes more important if the future direction of a value equivalent is pursued. `[2 * 2 * 2]` could be interpreted as `[2, 2, 2, 2]`, `[4, 4,]`, or `[8]`.
171
+
Since `of` cannot follow another identifier today, `[of of Int]` is unambiguous,[^type] but would clearly be hard to read. This is likely a hypothetical concern rather than a practical one since `of` is very rare as a variable name. The previous proposal's use of `x` involved a variable name that was more common.
135
172
136
-
Since `x`cannot follow another identifier today, `[x x Int]`is unambiguous,[^type] but would clearly be hard to read. This is likely a hypothetical concern rather than a practical one. While `x` is used often in scratch code for a local variable, a more meaningful name is usually preferable, and this would be especially the case if it is found being used for the size of an array literal. In addition, while `i`, `j`, or `n` are often legitimate counters that might be suited to the size of an array, `x` is generally not used for such things.
173
+
`x` is also less clear when used for the value version: `[5 x 5]` can be parsed unambiguously, but looks similar to five times five, and so visually has the same challenges as with the `*` operator, even if this isn't a problem for the compiler.
137
174
138
-
[^type]: or even `[x x x]`, since `x` can be a type name, albeit one that defies Swift's naming conventions.
175
+
[^type]: or even `[of of of]`, since `of` can be a type name, albeit one that defies Swift's naming conventions.
139
176
140
177
Another thing to consider is how that separator looks in the fully inferred version, which tend to start to look a little like ascii diagrams:
141
178
142
179
```
180
+
[_ of _]
143
181
[_ x _]
144
182
[_ * _]
145
183
[_; _]
146
-
[_ of _]
147
184
```
148
185
186
+
Of all these, the `of` choice is less susceptible to the ascii art problem.
187
+
149
188
### Order of size and type
150
189
151
190
The order of size first, then type is determined by the ordering of the unsugared type, and deviating from this for the sugared version is not an option.
@@ -156,6 +195,6 @@ In theory, when using integer literals or `_` the whitespace could be omitted (`
156
195
157
196
### Choice of brackets
158
197
159
-
`InlineArray` has a lot in common with tuples – especially in sharing "copy on copy" behavior, unlike regular `Array`. So `(5 x Int)` may be an appropriate alternative to the square brackets, echoing this similarity.
198
+
`InlineArray` has a lot in common with tuples – especially in sharing "copy on copy" behavior, unlike regular `Array`. So `(5 of Int)` may be an appropriate alternative to the square brackets, echoing this similarity. However, tuples and `InlineArray`s remain very different types, and it could be misleading to imply that `InlineArray` is "just" a tuple.
160
199
161
-
Beyond varying the separator, there may be other dramatically different syntax that moves further from the "like Array sugar, but with a size argument".
200
+
Beyond varying the separator, there may be other dramatically different syntax that moves further from the "like Array sugar, but with a size argument". For example, dropping the brackets altogether (i.e. `let a: 5 of Int`). However, these are probably too much of a departure from the current Swift idioms, so likely to cause further confusion without any real upside.
0 commit comments