I just opened a PR to propose new safe-loading API to load concrete integer values from RawSpan and family: Safe loading of integer values for `RawSpan` by glessard · Pull Request #3065 · swiftlang/swift-evolution · GitHub
Motivation
In [SE-0447][SE-0447], we introduced RawSpan along with some unsafe functions to load values of arbitrary types. While it is safe to load any of the native integer types with those functions, the unsafe annotation introduces an element of doubt for users of the standard library. Furthermore, controlling the endiannness for the loading operation is not available at the point of serialization, introducing further confusion. This proposal adds the ability to safely load integer values with ergonomic endianness control, without the doubt introduced by unsafe functions.
Proposed solution
RawSpan
RawSpan will gain a series of concretely typed load(as:) functions to obtain numeric values from the underlying memory, with no alignment requirement. These load(as:) functions can be safe because they return values from fully-inhabited types, meaning that these types have a valid value for every bit pattern of their underlying bytes.
The load(as:) functions will be bounds-checked, being a safe RawSpan API. For example,
extension RawSpan {
func load(fromByteOffset: Int = 0, as: UInt8.Type) -> UInt8
func load(
fromByteOffset: Int = 0, as: UInt16.Type, endianness: Endianness? = nil
) -> UInt16
}
@frozen
public enum Endianness: Equatable, Hashable, Sendable {
case big, little
}
The loadable types will be UInt8, Int8, UInt16, Int16, UInt32, Int32, UInt64, Int64, UInt, Int, Float32 (aka Float) and Float64 (aka Double). On platforms that support them, loading Float16, Float80, UInt128, and Int128 values will also be supported. These are not atomic operations.
The concrete load(as:) functions will not have equivalents with unchecked byte offset. If that functionality is needed, the generic unsafeLoad(fromUncheckedByteOffset:as:) is already available.
The load(as:) functions will also be available for MutableRawSpan and OutputRawSpan.
MutableRawSpan and OutputRawSpan
MutableRawSpan will gain a series of concretely typed storeBytes() functions that accept an endianness parameter, while OutputRawSpan will have matching append() functions:
extension MutableRawSpan {
mutating func storeBytes(
of value: UInt16,
toByteOffset offset: Int = 0,
as type: UInt16.Type,
endianness: Endianness
)
}
extension OutputRawSpan {
mutating func append(
_ value: UInt16,
as type: UInt16.Type,
endianness: Endianness
)
}
These functions do not have a default value for their endianness argument, as the existing generic MutableSpan.storeBytes(of:toByteOffset:as:) and OutputRawSpan.append(_:as:) functions use the native endianness, addressing this need.
These concrete implementations will support UInt16, Int16, UInt32, Int32, UInt64, Int64, UInt, Int, Float32 (aka Float) and Float64 (aka Double). On platforms that support them, Float16, Float80, UInt128, and Int128 values will also be supported. These are not atomic operations.
The concrete storeBytes(of:as:) functions will not have an equivalent with unchecked byte offset. If that functionality is needed, the generic storeBytes(of:toUncheckedByteOffset:as:) is already available.
Span
Span will gain a series of concrete initializers init(viewing: RawSpan) to allow viewing a range of untyped memory as a typed Span, when Span.Element is a numeric type. These conversions will check for alignment and bounds. For example,
extension Span {
@_lifetime(borrow span)
init(viewing bytes: borrowing RawSpan) where Element == UInt32
}
The supported element types will be UInt8, Int8, UInt16, Int16, UInt32, Int32, UInt64, Int64, UInt, Int, Float32 (aka Float) and Float64 (aka Double). On platforms that support them, initializing Span instances with Float16, Float80, UInt128, and Int128 elements will also be implemented.
The conversions from RawSpan to Span only support well-aligned views with the native endianness. The [swift-binary-parsing][swift-binary-parsing] package provides a more fully-featured ParserSpan type.
Please see the full proposal draft for the detailed design, alternatives and future directions.