[Pitch] Apply the `extracting()` slicing pattern to `Span` and `RawSpan`

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 of Span and RawSpan, 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.

7 Likes

Is there a plan to move forward with this?

This is now a proposal under review here: SE-0488: Apply the `extracting()` slicing pattern more widely

2 Likes