kennyc
(Kenny Carruthers)
1
Which types in Swift are considered trivial types and is there a Protocol, or Protocol composition, to restrict a generate parameter to only trivial types.
Consider a generic function for writing values into a raw buffer:
func write<T>(_ value:T) {
bufferPointer.storeBytes(of:value, toByteOffset:0, as:T.self)
}
The documentation for storeBytes<T>(of: T, toByteOffset: Int, as: T.Type) states that T must be a trivial type. However, the compiler doesn't seem to complain if I do something like this:
class Style {
var color = NSColor.red
var font = NSFont.labelFont(ofSize:11)
}
let style = Style()
write(style)
Style isn't something I'd consider to be a trivial type that .storeBytes can properly store, yet there's no error.
So what would be an appropriate constraint on write<T>(_ value:T) where T... such that only trivial types are allowed?
Joe_Groff
(Joe Groff)
2
Not currently. You could assert(_isPOD(T.self)) as a dynamic check.
1 Like
cukr
3
Do you think it would be a good idea to add that check to the storeBytes method when running in debug mode?
Joe_Groff
(Joe Groff)
4
Yeah, I know @Andrew_Trick is also interested in exposing a proper Trivial layout type constraint that could be used in generics as well. That would allow for load and storeBytes to work reliably with unaligned addresses too.
9 Likes
eskimo
(Quinn “The Eskimo!”)
5
In the meantime, you can make your generic function require that T conform to an empty protocol of your own devising, and than retroactively conform the types you want to support to that protocol.
protocol QWritable {
}
func write<T>(_ value: T) where T: QWritable {
}
extension Int: QWritable {
}
It’s a bit manual, but it’s safer.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
3 Likes
kennyc
(Kenny Carruthers)
6
Agreed, but of the existing protocols related to "numbers", which ones would be best to use to support:
- All Integer Types
- All Float Types
- All Bool Types
Are BinaryInteger and FloatingPoint the best options for the first two? What about for Bool or would it be better to just have a typed version of write(_ value:Bool) for that?
eskimo
(Quinn “The Eskimo!”)
7
Rather than enter the numeric protocol labyrinth, I would just do this on concrete types. Adding a conformance is simple, and you don’t need to add them all up front; you can just ‘fault’ them in as you need them.
Oh, and btw, despite my example above, I’m not totally comfortable with conforming Int to this protocol. Remember that Int is pointer sized and, conceptually at least, the sending and receiving processes could have different pointer sizes [1].
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
[1] On macOS this can’t happen because 32-bit macOS code runs on the old runtime, and Swift requires the modern runtime.
kennyc
(Kenny Carruthers)
8
Fair enough. So you'd recommend something like this then:
protocol MyTrivialType {}
extension Bool : MyTrivialType {}
extension Float32 : MyTrivialType {}
extension Float64 : MyTrivialType {}
extension Int8 : MyTrivialType {}
extension Int16 : MyTrivialType {}
extension Int32 : MyTrivialType {}
extension Int64 : MyTrivialType {}
extension UInt : MyTrivialType {}
extension UInt8 : MyTrivialType {}
extension UInt16 : MyTrivialType {}
extension UInt32 : MyTrivialType {}
extension UInt64 : MyTrivialType {}
And then based on your last comment, you would not define these:
extension Double : MyTrivialType {}
extension Float : MyTrivialType {}
extension Int : ByteCodingTrivialType {}
I'm ok with this solution, I was just originally curious if there was a more idiomatic way of doing it more Swiftly.
ole
(Ole Begemann)
9
The size of Float and Double is not platform-dependent like Int is. Float32 and Float64 are just typealiases for Float and Double, respectively.
kennyc
(Kenny Carruthers)
10
Good point. I'll just use Double and Float instead of their aliases.
Karl
(👑🦆)
11
I mean, it's not really important, but you could keep them like that. There's also Float80 (everywhere except Windows, I think?) and we'll probably get a Float16 at some point.
As that thread says, Float32/64 were supposed to be the "true" names and Float/Double were going to be aliases. Like I said, not important - just an interesting bit of history.
1 Like
CTMacUser
(Daryle Walker)
12
There have been requests for an AnyValue automatic protocol, that is a counter to AnyObject. Your idea would be an AnyTrival automatic protocol (and would refine AnyValue?).
Ben_Cohen
(Ben Cohen)
13
Any... would be the wrong prefix to use. That tends to be used to signify a type-erased supertype into which you can assign any of the subtypes, which isn't relevant here. The request is for a generic constraint (even if, coincidentally, you could also use it as en existential protocol).
1 Like
CTMacUser
(Daryle Walker)
14
You're thinking when we could (still can?) use "class" to specify a class constraint:
protocol ForClassesOnly1: AnyObject {} // we use nowadays for...
protocol ForClassesOnly2: class {} // ...this, the old(?) syntax.
So now, you would prefer something like:
protocol ForValueTypesOnly1: any !class // once "any" is a keyword
protocol ForValueTypesOnly2: any value // two new keywords
protocol ForTrivialTypesOnly: any trivial value // three new keywords
?
Ben_Cohen
(Ben Cohen)
15
No – AnyObject serves two purposes: as a constraint, and as a type-erased container for any class instance. The Any... part comes from the latter use. That latter use doesn't apply to trivial types, so I am saying it would be inappropriate for the constraint to be AnyTrivial i.e. it should just be Trivial.
2 Likes
Joe_Groff
(Joe Groff)
16
Even the name AnyObject is a bit of a wart, IMO. It would probably be more consistent to just call it Object.
1 Like
Karl
(👑🦆)
17
While that is typically true, there are other layout constraints which could act as a kind-of "supertype" (or superset, really) of trivial types. For example, compiler also supports exact-size and maximum-size trivial constraints. Would we do AnyTrivial(64)?
Apparently you can use them in @_specialize, and they actually work:
1 Like