- Proposal: SE-0101
- Author: Erica Sadun
- Status: Returned for Revision (Rationale)
- Review manager: Chris Lattner
Upon accepting SE-0096, the core team renamed the proposed stdlib function from dynamicType()
to type(of:)
to better comply with Swift's API guidelines.
This proposal renames sizeof
, sizeofValue
, strideof
, strideofValue
, align
, and alignOf
to emulate SE-0096's example.
Swift Evolution Discussion: [Pitch] Renaming sizeof, sizeofValue, strideof, strideofValue
Swift's API guidelines indicate that free-standing functions without side-effects should be named using a noun describing the returned value.
- Although
sizeof()
, etc are treated as terms of art, these names are appropriated from C. The functions do not correspond to anything namedsizeof
in LLVM. - All names are expanded to be more explanatory by prefixing
memory
and adopting lower camel case. Names are more often read than written, and the proposed names are more self-documenting. - As
stride
already has a well-established meaning in the standard library, this proposal changes its name tointerval
, matching existing documentation. - Via API guidance,
align
is renamed toalignment
. - SE-0096's
type(of:)
signature operates on instances. This aligns it withsizeofValue
,alignofValue
,strideofValue
, which operate on instances. Usingof
rather thanofValue
matches this behavior but at the cost of clarity. This proposal recommends amending SE-0096 to changetype(of:)
totype(ofValue:)
. - Improving type-call usability should take precedence over instance-calls. (See next bullet point.) Although
function(ofType:)
offers a natural correspondence to SE-0096, this proposal recommends omitting a label to enhance readability.memorySize
should be clear enough (and noun enough) to mitigate any issues of whether the name is or is not a noun. - 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 |
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.
/// Returns the contiguous memory footprint of `T`.
///
/// Does not include any dynamically-allocated or "remote" storage.
/// In particular, `memorySize(X.self)`, when `X` is a class type, is the
/// same regardless of how many stored properties `X` has.
public func memorySize<T>(_: T.Type) -> Int
/// Returns the contiguous memory footprint of `T`.
///
/// Does not include any dynamically-allocated or "remote" storage.
/// In particular, `memorySize(ofValue: a)`, when `a` is a class instance, is the
/// same regardless of how many stored properties `a` has.
public func memorySize<T>(ofValue: T) -> Int
/// Returns the least possible interval between distinct instances of
/// `T` in memory. The result is always positive.
public func memoryInterval<T>(_: T.Type) -> Int
/// Returns the least possible interval between distinct instances of
/// `T` in memory. The result is always positive.
public func memoryInterval<T>(ofValue: T) -> Int
/// Returns the minimum memory alignment of `T`.
public func memoryAlignment<T>(_: T.Type) -> Int
/// Returns the minimum memory alignment of `T`.
public func memoryAlignment<T>(ofValue: T) -> Int
Labels: This design omits labels for types. It uses ofValue
for values, assuming SE-0096 would update to match. This proposal recommends matching SE-0096 regardless of the core team choice: either of
or ofValue
.
Using Autoclosure: It may make sense to use @autoclosure
for value variants as the call shouldn't need its arguments evaluated:
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
Accepting Type Variations: The core team may choose omit the value variants entirely, replacing just three freestanding functions and removing the other three. In doing so, users must call type
on passed values. This pattern is already found in standard library code.
Current code:
let errnoSize = sizeof(errno.dynamicType)
Updated code:
let errnoSize = memorySize(type(ofValue:errno))
Pyry Jahkola points out one instance where the memorySize(type(of: …))
workaround won't work. When the value is an existential, it's illegal to ask for the size of its dynamic type: the result can't be retrieved at compile time:
// Swift 2.2, 64-bit
let i = 123
print(sizeofValue(i)) //=> 8
let c: CustomStringConvertible = i
print(sizeofValue(c)) //=> 40
print(sizeof(c.dynamicType)) // error: cannot invoke 'sizeof' with an argument list of type '(CustomStringConvertible.Type)'
On the other hand, dropping the ofValue:
variations allows SE-00096 to remain unamended.
This proposal requires migration support to rename keywords that use the old convention to adopt the new convention. This is a simple substitution with limited impact on existing code that is easily addressed with a fixit.
Dave Abrahams suggested rather than using global functions, the following design be considered:
MemoryLayout<T>.size // currently sizeof()
MemoryLayout<T>.spacing // currently strideof()
MemoryLayout<T>.alignment // currently alignof()
Dave further recommends that sizeofValue()
, strideofValue()
, and alignofValue()
be completely removed from Swift. Usage numbers from code searches (see above table) support his stance on their value, as instance types can be easily retrieved using type(of:)
. It is possible to use Dave's design and to retain value functions, as Matthew Johnson and Pyry Jahkola have laid out in on-list discussions.
In the rare times users consume memory layout functionality, using a MemoryLayout type reduces clarity. Consider the following examples, taken from Swift 3.0 stdlib files:
let errnoSize = sizeof(errno.dynamicType)
return sizeof(UInt) * 8
sendBytes(from: &address, count: sizeof(UInt.self))
_class_getInstancePositiveExtentSize(bufferClass) == sizeof(_HeapObject.self)
bytesPerIndex: sizeof(IndexType)
The proposed rewrite for these are:
let errnoSize = memorySize(ofValue: errno)
return memorySize(UInt.self) * 8
sendBytes(from: &address, count: memorySize(UInt.self))
_class_getInstancePositiveExtentSize(bufferClass) == memorySize(_HeapObject.self)
bytesPerIndex: memorySize(IndexType.self)
versus
let errnoSize = MemoryLayout.init(t: errno).size
return MemoryLayout<UInt>.size * 8
sendBytes(from: &address, count: MemoryLayout<UInt>.size)
_class_getInstancePositiveExtentSize(bufferClass) == MemoryLayout<_HeapObject.self>.size
bytesPerIndex: MemoryLayout<IndexType>.size
Swift adheres to a mantra of clarity. In each of the preceding examples, calling a function produces simpler code than using the Memory Layout approach:
- Early mention of the requested information: In functions the name (size, spacing/interval, alignment) are stated earlier, supporting reading code in one pass from left to right. Using properties delays recognition and causes the reader longer mental processing times.
- Simplicity of the function call: Calls devote the entirety of their name to describing what they do.
- Prominence of the type constructor: The eye is drawn to the MemoryLayout pattern. Using full type specification lends calls an importance and verbosity they don't deserve compared to their simpler counterparts.
Thank you, Xiaodi Wu, Matthew Johnson, Pyry Jahkola, Tony Allevato, Joe Groff, Dave Abrahams, and everyone else who contributed to this proposal