|
| 1 | +# Apply the extracting() slicing pattern more widely |
| 2 | + |
| 3 | +* Proposal: [SE-0488](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0488-extracting.md) |
| 4 | +* Author: [Guillaume Lessard](https://github.com/glessard) |
| 5 | +* Review Manager: [Tony Allevato](https://github.com/allevato) |
| 6 | +* Status: **Active Review (July 2–July 16, 2025)** |
| 7 | +* Implementation: underscored `_extracting()` members of `Span` and `RawSpan`, pending elsewhere. |
| 8 | +* Review: ([pitch](https://forums.swift.org/t/pitch-apply-the-extracting-slicing-pattern-to-span-and-rawspan/80322)) |
| 9 | + |
| 10 | +[SE-0437]: 0437-noncopyable-stdlib-primitives.md |
| 11 | +[SE-0447]: 0447-span-access-shared-contiguous-storage.md |
| 12 | +[SE-0467]: 0467-MutableSpan.md |
| 13 | +[Forum-LifetimeAnnotations]: https://forums.swift.org/t/78638 |
| 14 | + |
| 15 | + |
| 16 | +## Introduction and Motivation |
| 17 | + |
| 18 | +Slicing containers is an important operation, and non-copyable values have introduced a significant change in the spelling of that operation. When we [introduced][SE-0437] 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`][SE-0467]. |
| 19 | + |
| 20 | +Now that we have a [supported spelling][Forum-LifetimeAnnotations] 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][SE-0437]. |
| 21 | + |
| 22 | + |
| 23 | +## Proposed solution |
| 24 | + |
| 25 | +As previously discussed in [SE-0437][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. |
| 26 | + |
| 27 | +The family of `extracting()` methods established by the [`MutableSpan` proposal][SE-0467] is as follows: |
| 28 | +```swift |
| 29 | +public func extracting(_ bounds: Range<Index>) -> Self |
| 30 | +public func extracting(_ bounds: some RangeExpression<Index>) -> Self |
| 31 | +public func extracting(_: UnboundedRange) -> Self |
| 32 | +@unsafe public func extracting(unchecked bounds: Range<Index>) -> Self |
| 33 | +@unsafe public func extracting(unchecked bounds: ClosedRange<Index>) -> Self |
| 34 | + |
| 35 | +public func extracting(first maxLength: Int) -> Self |
| 36 | +public func extracting(droppingLast k: Int) -> Self |
| 37 | +public func extracting(last maxLength: Int) -> Self |
| 38 | +public func extracting(droppingFirst k: Int) -> Self |
| 39 | +``` |
| 40 | + |
| 41 | +These will be provided for the following standard library types: |
| 42 | +```swift |
| 43 | +Span<T> |
| 44 | +RawSpan |
| 45 | +UnsafeBufferPointer<T> |
| 46 | +UnsafeMutableBufferPointer<T> |
| 47 | +Slice<UnsafeBufferPointer<T>> |
| 48 | +Slice<UnsafeMutableBufferPointer<T>> |
| 49 | +UnsafeRawBufferPointer |
| 50 | +UnsafeMutableRawBufferPointer |
| 51 | +Slice<UnsafeRawBufferPointer> |
| 52 | +Slice<UnsafeMutableRawBufferPointer> |
| 53 | +``` |
| 54 | +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. |
| 55 | + |
| 56 | + |
| 57 | +## Detailed design |
| 58 | + |
| 59 | +The general declarations for these functions is as follows: |
| 60 | +```swift |
| 61 | +/// Returns an extracted slice over the items within |
| 62 | +/// the supplied range of positions. |
| 63 | +/// |
| 64 | +/// Traps if any position within the range is invalid. |
| 65 | +@_lifetime(copy self) |
| 66 | +public func extracting(_ bounds: Range<Index>) -> Self |
| 67 | + |
| 68 | +/// Returns an extracted slice over the items within |
| 69 | +/// the supplied range of positions. |
| 70 | +/// |
| 71 | +/// Traps if any position within the range is invalid. |
| 72 | +@_lifetime(copy self) |
| 73 | +public func extracting(_ bounds: some RangeExpression<Index>) -> Self |
| 74 | + |
| 75 | +/// Returns an extracted slice over all items of this container. |
| 76 | +@_lifetime(copy self) |
| 77 | +public func extracting(_: UnboundedRange) -> Self |
| 78 | + |
| 79 | +/// Returns an extracted slice over the items within |
| 80 | +/// the supplied range of positions. |
| 81 | +/// |
| 82 | +/// This function does not validate `bounds`; this is an unsafe operation. |
| 83 | +@unsafe @_lifetime(copy self) |
| 84 | +public func extracting(unchecked bounds: Range<Index>) -> Self |
| 85 | + |
| 86 | +/// Returns an extracted slice over the items within |
| 87 | +/// the supplied range of positions. |
| 88 | +/// |
| 89 | +/// This function does not validate `bounds`; this is an unsafe operation. |
| 90 | +@unsafe @_lifetime(copy self) |
| 91 | +public func extracting(unchecked bounds: ClosedRange<Index>) -> Self |
| 92 | + |
| 93 | +/// Returns an extracted slice over the initial elements |
| 94 | +/// of this container, up to the specified maximum length. |
| 95 | +@_lifetime(copy self) |
| 96 | +public func extracting(first maxLength: Int) -> Self |
| 97 | + |
| 98 | +/// Returns an extracted slice excluding |
| 99 | +/// the given number of trailing elements. |
| 100 | +@_lifetime(copy self) |
| 101 | +public func extracting(droppingLast k: Int) -> Self |
| 102 | + |
| 103 | +/// Returns an extracted slice containing the final elements |
| 104 | +/// of this container, up to the given maximum length. |
| 105 | +@_lifetime(copy self) |
| 106 | +public func extracting(last maxLength: Int) -> Self |
| 107 | + |
| 108 | +/// Returns an extracted slice excluding |
| 109 | +/// the given number of initial elements. |
| 110 | +@_lifetime(copy self) |
| 111 | +public func extracting(droppingFirst k: Int) -> Self |
| 112 | +``` |
| 113 | +For escapable types, the `@_lifetime` attribute is not applied. |
| 114 | + |
| 115 | + |
| 116 | +### Usage hints |
| 117 | + |
| 118 | +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: |
| 119 | + |
| 120 | +```swift |
| 121 | +@available(*, unavailable, renamed: "extracting(_ bounds:)") |
| 122 | +public subscript(bounds: Range<Index>) -> Self { extracting(bounds) } |
| 123 | + |
| 124 | +@available(*, unavailable, renamed: "extracting(first:)") |
| 125 | +public func droppingFirst(_ k: Int) -> Self { extracting(first: k) } |
| 126 | +``` |
| 127 | + |
| 128 | +## Source compatibility |
| 129 | +This proposal is additive and source-compatible with existing code. |
| 130 | + |
| 131 | +## ABI compatibility |
| 132 | +This proposal is additive and ABI-compatible with existing code. |
| 133 | + |
| 134 | +## Implications on adoption |
| 135 | +The additions described in this proposal require a new version of the Swift standard library. |
| 136 | + |
| 137 | +## Alternatives considered |
| 138 | +This is an extension of an existing pattern. We are not considering a different pattern at this time. |
| 139 | + |
| 140 | +## Future directions |
| 141 | +#### Disambiguation over ownership type |
| 142 | +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. |
| 143 | + |
| 144 | +## Acknowledgements |
| 145 | +Thanks to Karoy Lorentey and Tony Parker. |
| 146 | + |
0 commit comments