Introduction
Currently, subscripts can be read-only or they can be read-write, but there is no way to declare a subscript as write-only. Write-only subscripts have a number of important uses, and this proposal aims to bring them into the language.
Motivation
The MutableCollection
protocol overrides the Range
subscript from Collection
to allow assignment. However, because the return type of that subscript is SubSequence
, it follows that the only thing which can be assigned through that subscript is another SubSequence
:
var nums = Array(0..<5)
var x = [6, 7, 8]
nums[1..<4] = x // error
nums[1..<4] = x[...] // success
Conceptually, MutableCollection
allows the one-to-one replacement of individual elements, so it ought to be possible to assign any Sequence
of Element
s through that subscript, provided the lengths and element types are the same.
We can write a generic subscript to allow assigning any sequence to a range, and the corresponding version for RangeExpression
as well:
Generic subscript implementation
extension MutableCollection {
/// - Precondition: The number of elements must not change.
subscript<S: Sequence>(_ range: Range<Index>) -> S
where S.Element == Element
{
get { fatalError() }
set {
// TODO: Validate the precondition
var i = range.lowerBound
for x in newValue {
self[i] = x
formIndex(after: &i)
}
}
}
/// - Precondition: The number of elements must not change.
subscript<R: RangeExpression, S: Sequence>(_ range: R) -> S
where R.Bound == Index, S.Element == Element
{
get { fatalError() }
set { self[range.relative(to: self)] = newValue }
}
}
But there are some problems.
First, we had to use fatalError()
in the getters, because there is no way to construct a generic Sequence
from a slice of our collection. Indeed, we don’t want that anyway, we just want the setter.
Second, having both the generic RangeExpression
subscript and the current version exist simultaneously, produces “Ambiguous use of subscript” errors when calling the getter. Strangely, when the return-type is unspecified, there is no error, but when the return type is constrained to exactly match the existing subscript there is a compiler error for ambiguity:
var a = nums[2...4] // success
a = nums[2...4] // error: ambiguous use of subscript
let b: ArraySlice<Int> = nums[2...4] // error: ambiguous use of subscript
These difficulties would disappear if we could declare the generic subscripts as set-only. Then the only getter would be the one from the basic Collection
subscript, and everything would work as desired with no ambiguity and no danger of hitting the fatalError
in the generic getter.
Proposed solution
The proposed solution is to allow set-only subscripts.
This will make it possible to write the generic MutableCollection
assignment functionality described in the motivation section. It will also allow similar use-cases for other types, such as a Matrix
which has a dedicated Column
type: one subscript would be get-only and return a Column
, while another would be set-only to allow the assignment of any Sequence
to a column, provided the lengths and element types match.
The same approach would work with the currently-in-review proposal for discontiguous collection operations: the MutableCollection
subscript taking a RangeSet
could become generic and allow the assignment of any Sequence
with matching length and element type, instead of being restricted to only DiscontiguousSlice
.
Subscripts often exhibit this duality, where the getter should return a specific type and the setter should be generic. Set-only subscripts will enable this pattern in Swift.
Alternatives considered
One possible option is to introduce the access modifier private(get)
. This would not truly make the subscript set-only, but it would allow a set-only API to be published.
An expansion of the proposal could allow set-only computed properties as well.
Related discussion
Set-only computed properties (last post July 18, 2018)