oftentimes in programming we define types that contain smaller types. here’s an example of a small (4 byte) type:
/// A zero-indexed grid position within a source file. This type
/// can represent positions in files up to one million lines long,
/// and 4096 characters wide.
@frozen public
struct SourcePosition:RawRepresentable, Equatable, Hashable, Sendable
{
/// Contains the line number in the upper 20 bits, and
/// the column index in the lower 12 bits.
public
let rawValue:Int32
}
and here’s an example of a bigger type that contains a field of the smaller type:
@frozen public
struct SourceLocation<File>
{
public
let position:SourcePosition
public
let file:File
}
when we pass instances of the bigger type to other swift code, we usually do not think about layout; the compiler knows to load self.position at offset +0 and self.file at offset +4 (alignment notwithstanding).
but of course, when we serialize the bigger type to binary storage that doesn’t understand the swift ABI (e.g. BSON), we do need to think about layout, and we need to specify what SourceLocation<Int32> looks like when encoded to a raw type like Int64:
extension SourceLocation<Int32>:RawRepresentable
{
@inlinable public
var rawValue:Int64
{
.init(self.file) << 32 | .init(self.position.rawValue)
}
@inlinable public
init(rawValue:Int64)
{
self.init(
position: .init(rawValue: .init(truncatingIfNeeded: rawValue)),
file: .init(truncatingIfNeeded: rawValue >> 32))
}
}
can you see the bug?
answer
var rawValue:Int64
{
.init(self.file) << 32 | .init(self.position.rawValue)
// ^~~~~~~~~~~~~~~~~~~~~~
// UInt32.init(bitPattern: self.position.rawValue)
}
anyway, my point is writing this kind of “pack-and-crack” raw value logic is very error-prone, and i feel that our current toolbox of FixedWidthInteger API leaves a lot of legos on the floor to step on. because even if you get it right the first time, it is rather fragile, and the type system does not help you if you, for example, switch between Int32 and UInt32, or Int32 and Int16, etc.
can we get some standardized API for this stuff, maybe
infix operator .. : AdditionPrecedence
postfix operator ..
extension SourceLocation<Int32>:RawRepresentable
{
@inlinable public
var rawValue:Int64
{
self.file .. self.position.rawValue
}
@inlinable public
init(rawValue:Int64)
{
let (file, position):(Int32, Int32) = (rawValue)..
self.init(position: position, file: file)
}
}
?