Yes!
(and indeed Optional is just a special case).
Sketch implementation:
protocol PackableType { associatedtype Packed: PackedType }
protocol PackedType: Hashable {
associatedtype Unpacked: Hashable, PackableType
init(unpacked: Unpacked)
var unpacked: Unpacked { get }
}
extension Array where Element: PackedType {
subscript(unpack index: Index) -> Element.Unpacked {
get { self[index].unpacked }
set { self[index] = Element(unpacked: newValue) }
}
mutating func append(unpacked element: Element.Unpacked) {
append(Element(unpacked: element))
}
}
extension Dictionary where Value: PackedType {
subscript(unpack key: Key) -> Value.Unpacked? {
get { self[key]?.unpacked }
set {
guard let newValue else { self[key] = nil; return }
self[key] = Value(unpacked: newValue)
}
}
}
func writeBytes(_ source: UnsafeRawPointer, _ destination: UnsafeMutableRawPointer, _ bitOffset: inout Int, _ count: Int) {
precondition(bitOffset % 8 == 0, "TODO this case later")
let byteOffset = bitOffset / 8
// TODO: do the following better
let dst = destination.assumingMemoryBound(to: UInt8.self) + byteOffset
let src = source.assumingMemoryBound(to: UInt8.self)
for i in 0 ..< count { dst[i] = src[i] }
bitOffset += count * 8
}
func writeBits(_ source: UnsafeRawPointer, _ destination: UnsafeMutableRawPointer, _ bitOffset: inout Int, _ count: Int) {
preconditionFailure("TODO later")
bitOffset += count
}
func readBytes(_ destination: UnsafeMutableRawPointer, _ source: UnsafeRawPointer, _ bitOffset: inout Int, _ count: Int) {
precondition(bitOffset % 8 == 0, "TODO this case later")
let byteOffset = bitOffset / 8
// TODO: do the following better
let dst = destination.assumingMemoryBound(to: UInt8.self)
let src = source.assumingMemoryBound(to: UInt8.self) + byteOffset
for i in 0 ..< count { dst[i] = src[i] }
bitOffset += count * 8
}
func readBits(_ destination: UnsafeMutableRawPointer, _ source: UnsafeRawPointer, _ bitOffset: inout Int, _ count: Int) {
preconditionFailure("TODO later")
bitOffset += count
}
struct ExampleType: Hashable {
var a: Int8 = 0
var b: Int32 = 0
}
extension ExampleType: PackableType { // autogenerated somehow
struct Packed: PackedType {
typealias Unpacked = ExampleType // will be different for differnt types
typealias Bytes = (UInt8, UInt8, UInt8, UInt8, UInt8) // will be different for differnt types
var bytes: Bytes
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.unpacked == rhs.unpacked
}
func hash(into hasher: inout Hasher) {
hasher.combine(unpacked)
}
init(unpacked: ExampleType) {
var bitOffset = 0
var unpacked = unpacked // 😢 have to make a writeable copy for no good reason
// the following will be different for differnt types
bytes = (0, 0, 0, 0, 0) // 😢 have to initialize it first
writeBytes(&unpacked.a, &bytes, &bitOffset, 1)
writeBytes(&unpacked.b, &bytes, &bitOffset, 4)
}
var unpacked: ExampleType {
var bitOffset = 0
var bytes = bytes // 😢 have to make a writeable copy for no good reason
// the following will be different for differnt types
var unpacked = ExampleType() // 😢 have to initialize it first
readBytes(&unpacked.a, &bytes, &bitOffset, 1)
readBytes(&unpacked.b, &bytes, &bitOffset, 4)
return unpacked
}
}
}
var dict: [String: ExampleType.Packed] = [:]
var array: [ExampleType.Packed] = []
var a = ExampleType(a: 0x11, b: 0x22334455)
var b = ExampleType(a: 0x66, b: 0x778899AA)
array.append(unpacked: a)
array.append(unpacked: b)
// array at this point is: 11 55 44 33 22 66 aa 99 88 77
precondition(array[unpack: 1] == b)
dict[unpack: "hello"] = a
precondition(dict[unpack: "hello"] == a)
This sketch actually works! The use site required changes though...
var dict: [String: ExampleType.Packed] = [:]
var array: [ExampleType.Packed] = []
var a = ExampleType(a: 0x11, b: 0x22334455)
var b = ExampleType(a: 0x66, b: 0x778899AA)
array.append(unpacked: a)
array.append(unpacked: b)
// array memory at this point is:
// 11 55 44 33 22 66 aa 99 88 77
precondition(array[unpack: 1] == b)
dict[unpack: "hello"] = a
precondition(dict[unpack: "hello"] == a)
as instead of original ExampleType I have to use ExampleType.Packed to store in a collection and then instead of normal subscript operator, etc use the "unpack/unpacked" counterparts. which raises a question or three:
- perhaps I could do just
array.append(b.packed) or array[index].unpacked, etc
- can the use site be made looking normal, without those packs/unpacks?
- or could we embed it into collections implementation, which would check if element type adheres to some protocol similar to "Packable" above and do the pack/unpack business transparently?