- Proposal: SE-0101
- Authors: Erica Sadun, Dave Abrahams
- Review Manager: Chris Lattner
- Status: Implemented (Swift 3.0)
- Decision Notes: Rationale
This proposal addresses sizeof
, sizeofValue
, strideof
, strideofValue
, align
, and alignOf
. It discards the value-style standalone functions and combines the remaining items into a unified structure.
Review 1:
Prior Discussions:
- Swift Evolution Pitch: [Pitch] Renaming sizeof, sizeofValue, strideof, strideofValue
- Earlier Discussions
- SE-0101 Review
Although sizeof()
, etc are treated as terms of art, these names are appropriated from C. The functions do not correspond to anything named sizeof
in LLVM. Swift's six freestanding memory functions increase the API surface area while providing lightly-used and unsafe functionality.
These APIs are not like map
, filter
, and Dictionary
. They're specialty items that you should only reach for when performing unsafe operations, mostly inside the guts of higher-level constructs.
Refactoring this proposal to use a single namespace increases discoverability, provides a single entry point for related operations, and enables future expansions without introducing further freestanding functions.
This proposal introduces a new struct, MemoryLayout
/// Accesses the memory layout of `T` through its
/// `size`, `stride`, and `alignment` properties
public struct MemoryLayout<T> {
/// Returns the contiguous memory footprint of `T`.
///
/// Does not include any dynamically-allocated or "remote"
/// storage. In particular, `MemoryLayout<T>.size`, when
/// `T` is a class type, is the same regardless of how many
/// stored properties `T` has.
public static var size: Int { return _sizeof(T) }
/// For instances of `T` in an `Array<T>`, returns the number of
/// bytes from the start of one instance to the start of the
/// next. This is the same as the number of bytes moved when an
/// `UnsafePointer<T>` is incremented. `T` may have a lower minimal
/// alignment that trades runtime performance for space
/// efficiency. The result is always positive.
public static var stride: Int { return _strideof(T) }
/// Returns the default memory alignment of `T`.
public static var alignment: Int { return _alignof(T) }
}
With this design, consumers call:
// Types
MemoryLayout<Int>.size // 8
MemoryLayout<Int>.stride // 8
MemoryLayout<Int>.alignment // 8
This proposal removes sizeofValue()
, strideofValue()
, and alignofValue()
from the standard library. This proposal adopts the stance that sizes relate to types, not values.
Russ Bishop writes in the initial review thread, "Asking about the size of an instance implies things that aren’t true. Sticking value labels on everything doesn’t change the fact that sizeOf(swift_array)
is not going to give you the size of the underlying buffer no matter how you slice it."
As the following chart shows, type-based calls consistently outnumber instance-based calls in gist, github, and stdlib searches. The Google search for sizeof
is probably too general based on its use in other languages.
Term | stdlib search | gist search | Google site:github.com swift |
---|---|---|---|
sizeof | 157 | 169 | (18,600, term is probably too general) |
sizeofValue | 4 | 34 | 584 |
alignof | 44 | 11 | 334 |
alignofValue | 5 | 5 | 154 |
strideof | 24 | 19 | 347 |
strideofValue | 1 | 5 | 163 |
If for some reason, the core team decides that there's a compelling reason to include value calls, an implementation might look something like this:
extension MemoryLayout<T> {
init(_ : @autoclosure () -> T) {}
public static func of(_ candidate : @autoclosure () -> T) -> MemoryLayout<T>.Type {
return MemoryLayout.init(candidate).dynamicType
}
}
// Value
let x: UInt8 = 5
MemoryLayout.of(x).size // 1
MemoryLayout.of(1).size // 8
MemoryLayout.of("hello").stride // 24
MemoryLayout.of(29.2).alignment // 8
According to Joe Groff, concerns about existential values (it's illegal to ask for the size of an existential value's dynamic type) could be addressed by
"having
sizeof
and friends formally take an Any.Type instead of T.Type. (This might need some tweaking of the underlying builtins to be able to open existential metatypes, but it seems implementable.)"
This proposal uses <T>
/ T.Type
to reflect Swift's current implementation.
Note: There is a known bug (cite D. Gregor) that does not enforce .self
when used with sizeof
, allowing sizeof(UInt)
. This call should be sizeof(UInt.self)
. This proposal is written as if the bug were resolved without relying on adoption of SE-0090.
This proposal requires migration support to replace function calls with struct-based namespacing. This should be a simple substitution with limited impact on existing code that is easily addressed with a fixit.
The original proposal introduced three renamed standalone functions:
public func memorySize<T>(ofValue _: @autoclosure T -> Void) -> Int
public func memoryInterval<T>(ofValue _: @autoclosure T -> Void) -> Int
public func memoryAlignment<T>(ofValue _: @autoclosure T -> Void) -> Int
These functions offered human factor advantages over the current proposal but didn't address Dave's concerns about namespacing and overall safety. This alternative has been discarded and can be referenced by reading the original proposal.
Thank you, Xiaodi Wu, Matthew Johnson, Pyry Jahkola, Tony Allevato, Joe Groff, Russ Bishop, and everyone else who contributed to this proposal