Skip to content

Instantly share code, notes, and snippets.

@markmals
Last active November 12, 2024 16:44
Show Gist options
  • Save markmals/0dc943584bdcb7e65ac181f4621975e3 to your computer and use it in GitHub Desktop.
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
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
// 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