Dustpan is a small library that adds optional garbage collection to Swift.
One of the features that sets the Swift programming language apart from other languages is its use of Automatic Reference Counting (ARC). Many other modern programming languages use Garbage Collection (GC) instead.
Both ARC and GC are forms of automatic memory management. Memory that is no longer reachable from the running application is automatically freed. However, ARC has a limitation in that it cannot free object graphs that contain strong reference cycles. In ARC, such cycles must be broken manually.
Whereas ARC has its advantages (e.g. deterministic performance), there are problem domains where GC is very handy. Consider, for example, the case where you want to transpile a garbage collected programming language to Swift.
Dustpan was developed entirely in Swift and uses the Swift reflection API to walk the object graph. The specific GC algorithm used is Mark and Sweep.
Dustpan is mostly a toy project and its real world performance has not been analyzed. It should be expected to be quite slow.
Here's an example:
import Dustpan
class LL<T> {
var value: T
@Ref
var next: LL<T>?
init(_ value: T, _ next: LL<T>? = nil) {
self.value = value
self.next = next
}
}
class MyApp {
@Ref(root: true)
var list: LL<Int>? = nil
func makeCyclicList() {
let last = LL(4)
let first = LL(1, LL(2, LL(3, last)))
last.next = first
list = first
}
}
There are three steps to adopting Dustpan:
- Use a
Ref(root: true)
annotation to flag object graphs that should be garbage collected. - Inside the garbage collected object graphs, use a
Ref
annotation to break strong reference cycles. - Periodically, call
gc()
to free unreachable memory.
Do note that:
- Dustpan uses a stop-the-world algorithm. Therefore, while
gc
is running, there should be no mutations in the object graphs that are reachable from root references. - If you run into a “Fatal error: Unexpectedly found nil while unwrapping an Optional value” inside a
Ref
, this means thatgc
freed that particularRef
because there was noRef(root: true)
pointing to that part of the program. The fix is to add the missingRef(root: true)
annotation.