There's also the exclusivity angle, which is kind of interesting.
If you read the proposal, mutation through a keypath and offset pointer are pitched as being exactly the same:
var root: T, value: U
var key: WritableKeyPath<T, U>
// Mutation through the key path...
root[keyPath: \.key] = value
// ...is exactly equivalent to mutation through the offset pointer...
withUnsafePointer(to: &root) {
(UnsafeMutableRawPointer($0) + MemoryLayout<T>.offset(of: \.key))
// ...which can be assumed to be bound to the target type
.assumingMemoryBound(to: U.self).pointee = value
}
(Aside: I think there's a typo here - it should be withUnsafeMutablePointer(to: &root)
)
For structs this works, because in order to get the pointer to the root value, you must be in a scope where you are formally accessing it, and accessing the struct also access all of its stored properties. In other words, exclusivity is still enforced and these two ways of accessing the property are truly equivalent.
For classes, this is not the case. Each property must be formally accessed individually, and mutating through an offset pointer would bypass that and not enforce exclusivity. So they would not be equivalent - mutating through an offset pointer would be less safe, and there is no way to opt back in to runtime exclusivity enforcement to get that safety back.
You might say that's okay - after all, you can only use these offsets via unsafe pointers anyway, so you already have to manually enforce lots of preconditions for safe access. Also, this is really all for the benefit of C interop, which is unsafe in all kinds of other ways.
Maybe it matters, maybe it doesn't; it's a very particular difference between stored properties in structs and classes, and I find it interesting that the proposal mentions it. Presumably that inherent unsafety makes it less desirable to add to the standard library.