I’m working on a toy program that computes hashes of files. The user can choose from different hash algorithms—MD5, SHA1, etc. I therefore have a protocol HashAlgorithm that the different implementations conform to:
protocol HashAlgorithm {
/// Initializes a new hash algorithm state.
init()
/// The number of bytes in each block.
static var blockSize: Int { get }
/// The number of bytes in the final digest.
static var digestSize: Int { get }
/// Mixes a single block into this hash state.
mutating func iterate(blockBytes: UnsafeMutableRawBufferPointer)
/// Copies the final digest into the provided buffer and destroys the hash state.
consuming func finalize(into: UnsafeMutableRawBufferPointer)
}
All the buffer sizes must be checked at runtime. If HashAlgorithm were a generic struct, I could lift these constraints into the type system using InlineArray. SE-0452 doesn’t speak to the possibility of extending integer generics to protocols.
Given the subtle differences between generics and associated types, is it a sound thing to consider? Just trying to mock up a syntax raises some interesting questions:
protocol HashAlgorithm {
init()
static let blockSize: Int
static let digestSize: Int
// `let` requirements are currently not allowed in protocols.
// `var { get }` would allow the answer to change at runtime.
// Perhaps this even requires the compile-time constant expressions feature?
typealias Digest = InlineArray<digestSize, UInt8>
// Neither `typealias` nor `associatedtype` seems correct here.
// `typealias` seems wrong because the concrete type is dependent on the value of `digestSize`.
// `associatedtype` seems wrong because InlineArray is a concrete type, so it can’t be used as a constraint.
// Does this require a new hybrid syntax? `associatedtype Digest = InlineArray<digestSize, UInt8>`?
mutating func iterate(block: borrowing [digestSize * InlineArray])
// Does this pose parsing problems?
// Maybe `digestSize` needs to be fully qualified with `Self.`?
consuming func finalize() -> Digest
}
func hash<H: HashAlgorithm>(data: UnsafeRawBufferPointer, using _: H.Type) -> H.Digest {
var hashState = H.init()
var offset = 0
while offset < data.count {
let block = data[offset..<min(offset + H.blockSize, data.count)]
// The type of `block` can’t depend on the range argument.
hashState.iterate(block: block)
// How does the caller massage `block` into the appropriate `InlineArray<_, UInt8>` concrete type?
offset += algorithm.blockSize
}
return hashState.finalize()
}
let chosenAlgorithm: any HashAlgorithm.Type
chosenAlgorithm = askUser()
// What is the type of `chosenAlgorithm`’s methods?
let data = readFromInput()
let digest = hash(data: data, using: chosenAlgorithm)
// What is the type of `digest`?
print("\(digest)")