Skip to content

Instantly share code, notes, and snippets.

@dabrahams
Last active December 27, 2020 01:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dabrahams/9c5aec89c1daa2b64360782533f63118 to your computer and use it in GitHub Desktop.
Save dabrahams/9c5aec89c1daa2b64360782533f63118 to your computer and use it in GitHub Desktop.
How to efficiently create a mutable projection without inducing CoW
/// Returns `(source, transform(source.pointee))` and destroys `source.pointee`.
///
/// This is a low-level utility for creating mutable projections of values without
/// causing needless copy-on-write.
///
/// Typical usage:
///
/// func f(x: inout X) { // inout means we have exclusive access to `x`.
/// var (xAddress, xFrobnication)
/// = unsafeMoveMap(destroying: &x) { x.frobnication() }
///
/// // `xFrobnication` is a mutable projection of `x`
/// somethingWith(&xfrobnication) // mutate it
///
/// xAddress.initialize(to: X(defrobnicating: xFrobnication))
/// }
///
private func unsafeMoveMap<T, U>(
destroying source: UnsafeMutablePointer<T>, transform: (T)->U
) -> (address: UnsafeMutablePointer<T>, value: U)
{
return (source, transform(source.move()))
}
/// A nominal version of the tuple type that is the `Element` of
/// `Swift.Dictionary`.
public struct KeyValuePair<Key, Value> {
/// Creates an instance with the given key and value.
public init(key: Key, value: Value) {
(self.key, self.value) = (key, value)
}
public var key: Key
public var value: Value
}
/// Interop with the corresponding non-nominal tuple
extension KeyValuePair {
/// Constructs a nominally-typed version of `x`.
public init(_ x: (key: Key, value: Value)) {
self.init(key: x.key, value: x.value)
}
/// A read/write projection of `self`
public var tuple: (key: Key, value: Value) {
get { (key, value) }
set {
key = newValue.key
value = newValue.value
}
// Comment out the _modify accessor to experience the CoW.
_modify {
// The LOE says nothing else can touch `self` while we're in _modify, so
// we can safely destroy it as long as we put it back before we're done.
var (p, kv) = unsafeMoveMap(destroying: &self) { $0.tuple }
defer { p.initialize(to: .init(kv)) }
yield &kv
}
}
}
/// Demonstration; optimization not required.
func address<T>(_ p: UnsafeMutablePointer<T>) -> Int {
return .init(bitPattern: p)
}
func test1(_ kv: inout KeyValuePair<Int, [Int]>) {
let before = kv.value.withUnsafeMutableBufferPointer { $0.baseAddress! }
kv.tuple.value[0] = 3
let after = kv.value.withUnsafeMutableBufferPointer { $0.baseAddress! }
if before != after { print("CoW occurred.") }
else { print("CoW avoided") }
}
func test() {
var kv = KeyValuePair(key: 3, value: [1, 2, 3])
test1(&kv)
if kv.value[0] != 3 { print("Something went wrong.") }
}
test()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment