Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions Sources/FoundationEssentials/Data/ContiguousBytes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@

//===--- ContiguousBytes --------------------------------------------------===//

#if compiler(>=6.2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Foundation only compiles with the most recent released compiler + main, so I don't think this check is required.

/// Indicates that the conforming type is a contiguous collection of raw bytes
/// whose underlying storage is directly accessible by withUnsafeBytes.
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
public protocol ContiguousBytes: ~Escapable, ~Copyable {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just confirming, are there any ABI implications on this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it is not ABI-breaking (or source-breaking) to make this change.

/// Calls the given closure with the contents of underlying storage.
///
/// - note: Calling `withUnsafeBytes` multiple times does not guarantee that
/// the same buffer pointer will be passed in every time.
/// - warning: The buffer argument to the body should not be stored or used
/// outside of the lifetime of the call to the closure.
func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R
Copy link
Contributor

Choose a reason for hiding this comment

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

There's probably still work to be done on this protocol to update it (it doesn't allow for producing a span, it hasn't adopted typed throws, etc.) but this seems like a reasonable step in a good direction that we can do without fully fleshing out those changes at the moment if we want to update this incrementally

Copy link
Member Author

Choose a reason for hiding this comment

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

Right. The best we can do with the protocol as-is (without breaking existing clients) would be to add a withSpan, because that's implementable by going through withUnsafeBytes.

}
#else
/// Indicates that the conforming type is a contiguous collection of raw bytes
/// whose underlying storage is directly accessible by withUnsafeBytes.
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
Expand All @@ -24,6 +38,7 @@ public protocol ContiguousBytes {
/// outside of the lifetime of the call to the closure.
func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R
}
#endif

//===--- Collection Conformances ------------------------------------------===//

Choose a reason for hiding this comment

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

Should the array types implement withBytes(_:) instead of using the default implementation?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that's reasonable. For Array and ArraySlice, it's just a matter of completeness right now, because we still have to go through withUnsafeBufferPointer. For ContiguousArray, we can use span because it back-deploys.


Expand Down Expand Up @@ -109,3 +124,34 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes {
}
}
}

#if compiler(>=6.2)

//===--- Span Conformances -----------------------------------------===//

@available(FoundationPreview 6.3, *)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a public conformance, right? We would need an API review.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. Here's the proposal PR: #1582

extension RawSpan: ContiguousBytes {
}

@available(FoundationPreview 6.3, *)
extension MutableRawSpan: ContiguousBytes {
}

@available(FoundationPreview 6.3, *)
extension Span: ContiguousBytes where Element == UInt8 {
}

@available(FoundationPreview 6.3, *)
extension MutableSpan: ContiguousBytes where Element == UInt8 {
}

@available(FoundationPreview 6.3, *)
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
Copy link
Contributor

Choose a reason for hiding this comment

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

The double availability attribute seems a bit suspicious here - each declaration should only have a single availability. Is the problem here the macOS back deployment build of the package? If so, we'll need to create a new availability macro (something like FoundationInlineArray) that maps to macOS 26-aligned in the package manifest / CMake files

Copy link
Member Author

Choose a reason for hiding this comment

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

This worked out, thank you for the suggestion

extension InlineArray: ContiguousBytes where Element == UInt8 {
@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
return try span.withUnsafeBytes(body)
}
}

#endif
41 changes: 41 additions & 0 deletions Tests/FoundationEssentialsTests/ContiguousBytesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Testing

#if canImport(FoundationEssentials)
@testable import FoundationEssentials
#else
@testable import Foundation
#endif

#if compiler(>=6.2)

func acceptContiguousBytes(_ bytes: borrowing some ContiguousBytes & ~Escapable & ~Copyable) { }

@Suite("ContiguousBytesTests")
private struct ContiguousBytesTests {
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
@Test func span() throws {
if #available(FoundationPreview 6.3, *) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the function is already annotated as only available on macOS 26+, I don't think we need this #available check here. In the end what we probably want is for this function to be annotated as @available(FoundationInlineArray 6.3, *)

Copy link
Member Author

Choose a reason for hiding this comment

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

I was able to clean this up with your prior suggestion

var bytes: [UInt8] = [1, 2, 3]
acceptContiguousBytes(bytes.span)
acceptContiguousBytes(bytes.mutableSpan)
acceptContiguousBytes(bytes.span.bytes)

let ms = bytes.mutableSpan
acceptContiguousBytes(ms.bytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
acceptContiguousBytes(ms.bytes)
acceptContiguousBytes(ms.mutableBytes)

Copy link
Member Author

Choose a reason for hiding this comment

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

I actually want to do both, thanks!

}
}
}

#endif