The way I understand `protect` and `dispose` is they are the inter-thread
equivalent to `GC_ref` and `GC_unref` respectively.
Nim's per-thread GC only looks for roots in the current thread. If some objects
can't be tracked using traced `ref`'s, i.e. using typed `ptr`'s or raw
`pointer`'s, then you can use `GC_ref` and `GC_unref` to manually manage these
untraced objects in the same thread. The typical use-case for this is when
passing objects through FFI to C code.
When GC objects are passed across threads through `ptr` or `pointer`, then
`protect` and `dispose` do the same thing, except they are thread-safe
(GC_ref/unref can only be used in same thread). You obtain a `ForeignCell`
handle from the thread that owns the object -- this ensures the object won't be
inadvertently collected -- and then when you're done using it in the other
thread(s), you call `dispose` on the `ForeignCell` to convey to the original GC
thread that it can now collect the object if it's no longer in use.