Skip to content

[pitch] Span-providing properties in the standard library #2620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jan 16, 2025

Conversation

glessard
Copy link
Contributor

Continuing the work of adding Span, this is a proposal to add the ability to use Span as computed properties on standard library types, including Array.

var bytes: RawSpan { get }
}

extension String.UTF8View {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: What is the behavior (and what are the performance characteristics) of this operation when a String has storage other than contiguous UTF-8? Would it be desirable to return an optional, or alternatively require the user to know or have called makeContiguousUTF8() on the String as a precondition?

Copy link
Contributor Author

@glessard glessard Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behind the scenes, we are implementing a new "lazy eager bridging" behaviour for bridged Array and bridged String. They will always succeed, but they will sometimes entail an allocation and copy (i.e. "usually O(1), sometimes O(n)".) An initial step is here. For native non-bridged instances, always O(1).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! The implementation details would of course be out of scope for this text, but might be worth calling out here the performance implications for calling storage and bytes on these types, pros and cons for usability vs predictability, etc. (given that this is supposed to be a safe, performant API).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appended a paragraph to the detailed design to spell this out.

@glessard glessard force-pushed the stdlib-span-properties branch from 84708d7 to 670ac2d Compare November 20, 2024 02:03
@rjmccall rjmccall added the LSG Contains topics under the domain of the Language Steering Group label Dec 9, 2024
@glessard glessard force-pushed the stdlib-span-properties branch from 8a7d1ff to 49668a2 Compare December 18, 2024 00:48
- these properties are not expressible in the current type system.
- if we don’t get the generic ones, we should remove the raw ones too.
}
```

All of these unsafe conversions return a value whose lifetime is dependent on the _binding_ of the UnsafeBufferPointer. Note that this does not keep the underlying memory alive, as usual where the `UnsafePointer` family of types is involved. The programmer must ensure the following invariants for as long as the `Span` or `RawSpan` binding is valid:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence is somewhat mysterious:

All of these unsafe conversions return a value whose lifetime is dependent on the binding of the UnsafeBufferPointer.

Here's an attempted concrete explanation...

Swift does not manage the lifetime of unsafe buffer values. Consequently, when an unsafe buffer type provides a storage property, the resulting Span value depends on the variable that the unsafe buffer is bound to. The compiler enforces that the span does not escape the lexical scope of the variable that it depends on. It is up to the programmer to ensure that the unsafe buffer is valid within that lexical scope, which is typically true.

For example, if the unsafe buffer is a function argument, then it's storage is valid within the function body and can even be returned if the function's result depends on the argument:

@lifetime(borrow ubp)
func returnUBPStorage(ubp: UnsafeRawBufferPointer) -> RawSpan {
  return ubp.storage // OK: 'span' can be returned since the function's return value also has a dependence on 'ubp'.
}

It is an error to use the unsafe buffer's storage outside of it's variable's lexical scope, regardless of the buffer's origin:

@lifetime(borrow ubp)
func copyUBPStorage(ubp: UnsafeRawBufferPointer) -> RawSpan {
  let localBuffer = ubp
  return localBuffer.storage // ERROR: lifetime-dependent value escapes its scope
                             //        it depends on the lifetime of variable 'localBuffer'
}

func escapeUBPStorage(array: [Int64]) {
  var span = RawSpan()
  array.withUnsafeBytes {
    span = $0.storage // ERROR: lifetime-dependent value escapes its scope
                      //        it depends on the lifetime of argument '$0'
  }
  read(span)
}

@DougGregor
Copy link
Member

I'll manage this review

@DougGregor DougGregor merged commit 695000a into swiftlang:main Jan 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LSG Contains topics under the domain of the Language Steering Group
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants