Hello,
Here is a targeted proposal to add the established extracting()
slicing pattern to the Span
and RawSpan
types, as well as rounding it out for the UnsafeBufferPointer
family.
Apply the extracting()
slicing pattern more widely
- Proposal: TBD
- Author: Guillaume Lessard
- Review Manager: TBD
- Status: Pitch
- Implementation: underscored
_extracting()
members ofSpan
andRawSpan
, pending elsewhere. - Review: Pending
Introduction and Motivation
Slicing containers is an important operation, and non-copyable values have introduced a significant change in the spelling of that operation. When we introduced non-copyable primitives to the standard library, we allowed slicing UnsafeBufferPointer
and related types via a family of extracting()
methods. We expanded upon these when introducing MutableSpan
.
Now that we have a stable spelling for lifetime dependencies, we propose adding the extracting()
methods to Span
and RawSpan
, as well as members of the UnsafeBufferPointer
family that were missed in SE-0437.
Proposed solution
As previously discussed in SE-0437, the slicing pattern established by the Collection
protocol cannot be generalized for either non-copyable elements or non-escapable containers. The solution is a family of functions named extracting()
, with appropriate argument labels.
The family of extracting()
methods established by the MutableSpan
proposal is as follows:
public func extracting(_ bounds: Range<Index>) -> Self
public func extracting(_ bounds: some RangeExpression<Index>) -> Self
public func extracting(_: UnboundedRange) -> Self
@unsafe public func extracting(unchecked bounds: Range<Index>) -> Self
@unsafe public func extracting(unchecked bounds: ClosedRange<Index>) -> Self
public func extracting(first maxLength: Int) -> Self
public func extracting(droppingLast k: Int) -> Self
public func extracting(last maxLength: Int) -> Self
public func extracting(droppingFirst k: Int) -> Self
These will be provided for the following standard library types:
Span<T>
RawSpan
UnsafeBufferPointer<T>
UnsafeMutableBufferPointer<T>
Slice<UnsafeBufferPointer<T>>
Slice<UnsafeMutableBufferPointer<T>>
UnsafeRawBufferPointer
UnsafeMutableRawBufferPointer
Slice<UnsafeRawBufferPointer>
Slice<UnsafeMutableRawBufferPointer>
Some of the types in the list above already have a subset of the extracting()
functions; their support will be rounded out to the full set.
Detailed design
The general declarations for these functions are as follows:
/// Returns an extracted slice over the items within
/// the supplied range of positions.
///
/// Traps if any position within the range is invalid.
@_lifetime(copy self)
public func extracting(_ byteOffsets: Range<Int>) -> Self
/// Returns an extracted slice over the items within
/// the supplied range of positions.
///
/// Traps if any position within the range is invalid.
@_lifetime(copy self)
public func extracting(_ byteOffsets: some RangeExpression<Int>) -> Self
/// Returns an extracted slice over all items of this container.
@_lifetime(copy self)
public func extracting(_: UnboundedRange) -> Self
/// Returns an extracted slice over the items within
/// the supplied range of positions.
///
/// This function does not validate `bounds`; this is an unsafe operation.
@unsafe @_lifetime(copy self)
public func extracting(unchecked bounds: Range<Index>) -> Self
/// Returns an extracted slice over the items within
/// the supplied range of positions.
///
/// This function does not validate `bounds`; this is an unsafe operation.
@unsafe @_lifetime(copy self)
public func extracting(unchecked bounds: ClosedRange<Index>) -> Self
/// Returns an extracted slice over the initial elements
/// of this container, up to the specified maximum length.
@_lifetime(copy self)
public func extracting(first maxLength: Int) -> Self
/// Returns an extracted slice excluding
/// the given number of trailing elements.
@_lifetime(copy self)
public func extracting(droppingLast k: Int) -> Self
/// Returns an extracted slice containing the final elements
/// of this container, up to the given maximum length.
@_lifetime(copy self)
public func extracting(last maxLength: Int) -> Self
/// Returns an extracted slice excluding
/// the given number of initial elements.
@_lifetime(copy self)
public func extracting(droppingFirst k: Int) -> Self
All of the above operate in constant time.
For escapable types, the @_lifetime
attribute is not applied.
Usage hints
The extracting()
pattern, while not completely new, is still a departure over the slice pattern established by the Collection
protocol. For Span
, RawSpan
, MutableSpan
and MutableRawSpan
, we can add unavailable subscripts and function with hints towards the corresponding extracting()
function:
@available(*, unavailable, renamed: "extracting(_ bounds:)")
public subscript(bounds: Range<Index>) -> Self { extracting(bounds) }
@available(*, unavailable, renamed: "extracting(first:)")
public func droppingFirst(_ k: Int) -> Self { extracting(first: k) }
Source compatibility
This proposal is additive and source-compatible with existing code.
ABI compatibility
This proposal is additive and ABI-compatible with existing code.
Implications on adoption
The additions described in this proposal require a new version of the Swift standard library.
Alternatives considered
This is an extension of an existing pattern. We are not considering a different pattern at this time.
Future directions
Disambiguation over ownership type
The extracting()
functions proposed here are borrowing. MutableSpan
has versions defined as mutating, but it could benefit from consuming ones as well. In general there could be a need for all three ownership variants of a given operation (borrowing
, consuming
, or mutating
.) In order to handle these variants, we could establish a pattern for disambiguation by name, or we could invent new syntax to disambiguate by ownership type. This is a complex topic left to future proposals.
Acknowledgements
Thanks to Karoy Lorentey and Tony Parker.