I'm finding myself needing a facility to access specific elements in a variadic tuple from time to time.
Currently, I'm doing something like this:
struct VariadicTupleAccessor<each Element>: Sendable {
init() {
var offsetCursor = 0
func referenceNextElement<T>(as type: T.Type) -> ElementReference<T> {
let alignment = MemoryLayout<T>.alignment
let offset = (offsetCursor + alignment - 1) & ~(alignment - 1)
offsetCursor = offset
return ElementReference(offset: offset)
}
elementReferences = (repeat referenceNextElement(as: (each Element).self))
}
let elementReferences: (repeat ElementReference<each Element>)
struct ElementReference<T> {
fileprivate let offset: Int
}
func mutate<T, Result>(
_ reference: ElementReference<T>,
on tuple: inout (repeat each Element),
body: (inout T) throws -> Result
) rethrows -> Result {
try withUnsafeMutableBytes(of: &tuple) { buffer in
assert(reference.offset < buffer.count)
let pointer = (buffer.baseAddress! + reference.offset)
.assumingMemoryBound(to: T.self)
return try body(&pointer.pointee)
}
}
}
Is there a current or future language feature that can make this possible without unsafe pointers?
Does the logic I have here make sense? It makes some assumptions about the ABI of tuples but I think that is OK?
As far as I know, the ABI of tuples (in general, but particularly for heterogeneous element types) is not guaranteed, and therefore unsafe to make assumptions about.
Are you against using key paths (or closures) for this? You'd get something similar in effect — a key-like reference you can carry around to look up an associated value.
You need to advance offsetCursor = offset + MemoryLayout<T>.size to advance past the current element.
Thanks for catching this, you are correct! I'm currently fighting some compiler crashes so haven't actually been able to run this code yet...
As @mattcurtis noted, this looks roughly WritableKeyPath shaped, so it would be interesting to hear if key paths aren't sufficient for your use case.
This is interesting, because I also thought that key paths would be the correct API to get this to work. I just don't think that in modern swift there is a way to get to a WritableKeyPath from a (repeat each T). Essentially I want to iterate a parameter pack and get a WritableKeyPath<(repeat each T), SpecificT> for each element in the parameter pack, but I'm not sure this is something that is possible right now, hence the memory-offset-calculation-shenanigans.
Here's the two places I actually use this pattern in my code at the moment:
Neither of these work now, the tuple example causes a compiler crash and the object properties example can't prove that the properties pack is the same shape as the references pack, but those are unrelated issues I just haven’t had a moment to fix yet.
Is there a way to say “map this tuple, but just change this one element”? The way I see it if you have a variadic tuple (a, b, c, …z) you can convert it to (foo(a), foo(b), foo(c), …foo(z)) but there is no way to express “change just b to foo(b)” when you don’t have any handle for b other than the variadic parameter.
The way you suggested kinda works, but is indeed inefficient and has its own unsafety (force casting).
Yeah... there are things that can make it safer, like comparing types, but either way you'll have to find a means to workaround the limitation of not being able to dynamically index and mutate individual values of tuples.
Thanks!
One thing I'd be worried about with this design is that it seems pretty easy to accidentally subscript as the wrong type which would lead to undefined behavior.
I had a more-type safe variant but I had to do a ton of nasty stuff to get the compiler to not crash and to believe that the pack of accessors and the pack of values was the same shape.