Last active
November 12, 2024 16:44
-
-
Save markmals/0dc943584bdcb7e65ac181f4621975e3 to your computer and use it in GitHub Desktop.
An approximation of Rust & C++ smart pointers in Swift with non-copyable types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public protocol Pointer<Pointee>: ~Copyable { | |
associatedtype Pointee | |
var pointee: Pointee { get nonmutating set } | |
} | |
public struct UniquePointer<Pointee>: ~Copyable, Pointer { | |
private let memory: UnsafeMutablePointer<Pointee> | |
public var pointee: Pointee { | |
get { memory.pointee } | |
nonmutating set { memory.pointee = newValue } | |
} | |
public init(_ value: Pointee) { | |
self.memory = UnsafeMutablePointer<Pointee>.allocate(capacity: MemoryLayout<Pointee>.size) | |
self.memory.initialize(to: value) | |
} | |
deinit { | |
print("Deinit UniquePointer(\(pointee))") | |
memory.deallocate() | |
} | |
} | |
public struct SharedPointer<Pointee>: ~Copyable, Pointer { | |
internal let references: UnsafeMutablePointer<Int> | |
internal let memory: UnsafeMutablePointer<Pointee> | |
public var pointee: Pointee { | |
get { memory.pointee } | |
nonmutating set { memory.pointee = newValue } | |
} | |
private init(_ memory: UnsafeMutablePointer<Pointee>, _ references: UnsafeMutablePointer<Int>) { | |
self.memory = memory | |
self.references = references | |
} | |
@_disfavoredOverload | |
public init(_ value: Pointee) { | |
let memory = UnsafeMutablePointer<Pointee>.allocate(capacity: MemoryLayout<Pointee>.size) | |
memory.initialize(to: value) | |
let counter = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.size) | |
counter.initialize(to: 1) | |
self.init(memory, counter) | |
} | |
public init(_ pointer: borrowing SharedPointer) { | |
pointer.references.pointee += 1 | |
self.init(pointer.memory, pointer.references) | |
} | |
deinit { | |
print("Deinit SharedPointer(\(pointee))") | |
references.pointee -= 1 | |
if references.pointee == 0 { | |
memory.deallocate() | |
} | |
} | |
} | |
public struct WeakPointer<Pointee>: Pointer { | |
private let memory: UnsafeMutablePointer<Pointee> | |
public var pointee: Pointee? { | |
get { memory.pointee } | |
nonmutating set { | |
if let newValue { | |
memory.pointee = newValue | |
} | |
} | |
} | |
public init(_ pointer: borrowing SharedPointer<Pointee>) { | |
self.memory = pointer.memory | |
} | |
} | |
// MARK: Usage | |
// `if true` blocks to demonstrate scope-based deinitialization | |
if true { | |
let a = UniquePointer(5) | |
print("a", a.pointee) | |
if true { | |
let b = UniquePointer(a.pointee) | |
b.pointee = b.pointee + 1 | |
print("a", a.pointee) | |
print("b", b.pointee) | |
} // `b` goes out of scope; `b` is deinitialized | |
let c = SharedPointer(5) // Reference count initialized at 1 | |
print("c", c.pointee) | |
if true { | |
let d = SharedPointer(c) // Reference count increases to 2 | |
d.pointee = d.pointee + 1 | |
print("c", c.pointee) | |
} // `d` goes out of scope; `d` is deinitialized & reference count is decremented to 1 | |
// Weak pointers for avoiding retain cycles | |
let w = WeakPointer(c) | |
// WeakPointer.pointee is Optional, as it might be deallocated before WeakPointer is | |
print(w.pointee) | |
} | |
// `c` goes out of scope; `c` is deinitialized & reference count is decremented to 0 | |
// `a` goes out of scope; `a` is deinitialized |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Pointer read | |
prefix operator * | |
prefix func * <T, P: Pointer<T> & ~Copyable>(pointer: borrowing P) -> T { | |
return pointer.pointee | |
} | |
// Pointer dereference | |
postfix operator | | |
postfix func | <T, P: Pointer<T> & ~Copyable>(pointer: borrowing P) -> T { | |
return pointer.pointee | |
} | |
// Pointer write | |
infix operator *= | |
public func *= <T, P: Pointer<T> & ~Copyable>(pointer: borrowing P, newValue: T) { | |
pointer.pointee = newValue | |
} | |
// SharedPointer retain | |
prefix operator ~ | |
prefix func ~ <T>(pointer: borrowing SharedPointer<T>) -> SharedPointer<T> { | |
return SharedPointer(pointer) | |
} | |
// SharedPointer weak ref | |
prefix operator ^ | |
prefix func ^ <T>(pointer: borrowing SharedPointer<T>) -> WeakPointer<T> { | |
return WeakPointer(pointer) | |
} | |
// MARK: Usage | |
// `if true` blocks to demonstrate scope-based deinitialization | |
if true { | |
let a = UniquePointer(5) | |
print("a", *a) | |
if true { | |
let b = UniquePointer(*a) | |
b *= (*b + 1) | |
print("a", *a) | |
print("b", *b) | |
} // `b` goes out of scope; `b` is deinitialized | |
let c = SharedPointer(5) // Reference count initialized at 1 | |
print("c", *c) | |
if true { | |
let d = ~c // Reference count increases to 2 | |
d *= (*d + 1) | |
print("c", *c) | |
} // `d` goes out of scope; `d` is deinitialized & reference count is decremented to 1 | |
// Weak pointers for avoiding retain cycles | |
let w = ^c | |
// *w is Optional, as it might be deallocated before w is | |
print(*w) | |
} | |
// `c` goes out of scope; `c` is deinitialized & reference count is decremented to 0 | |
// `a` goes out of scope; `a` is deinitialized |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment