Reputation: 4154
I want to implement a weak reference in Go such that I can use a finalizer to detect when a data structure is no longer required and be able to store/clean up data.
One way I have found to do it is to use a uintptr as a map key such that when a finalizer is called, I can access/cleanup the data using the pointer value passed to the finalizer function. Is this safe to do?
I think my question is: does Go use a moving garbage collector? Or will it?
Upvotes: 3
Views: 2276
Reputation: 6556
Go right now is doesn't have a moving GC but there's no guarantee that it will stay this way forever. If you want to rely on this aspect, you can import this library made by one of Go contributors:
import _ "go4.org/unsafe/assume-no-moving-gc"
It will crash your program if Go ever switches to a moving GC.
Upvotes: 1
Reputation: 4154
For completeness this is what I wound up doing something like:
type actualThing struct {
Data int
}
type internalHandle struct {
*actualThing
}
type ExternalHandle struct {
*internalHandle
}
So a user of ExternalHandle
can still write code like ExternalHandle.Data
but the internal maintenance code can still atomically update *actualThing
. A finalizer on a pointer to an ExternalHandle
will signal the rest of the stack to delete references to internalHandle
and stop propagating updates to it.
Basically the super convenient thing is that by nesting the structs as such, users of ExternalHandle
use it without realizing or dealing with the fact that it's a double pointer dereference.
Upvotes: 2
Reputation: 121099
The unsafe documentation allows for GC that moves values in memory.
A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.
Upvotes: 2
Reputation: 489293
The link that you yourself quoted in a comment provides the answer to the question you meant to ask: instead of giving clients a pointer to the underlying object that you'd like to fuss with, give them a pointer to a wrapper object:
type internal struct {
// all the internal stuff goes here
}
// change the name Wrapper below to something more suitable
type Wrapper struct {
*internal // or p *internal if you want to be overly verbose
}
func NewWhatever(/*args*/) *Wrapper {
p := &Wrapper{...} // fill this part in
runtime.SetFinalizer(p, wrapperGotCollected)
return p
}
func wrapperGotCollected(p *Wrapper) {
// since p itself is about to be collected,
// **p (or *((*p).p)) is no longer accessible by
// the user who called NewWhatever(). Do
// something appropriate here.
}
Note that this does not use a finalizer on the *internal
, but rather a finalizer on the *Wrapper
: at the time that wrapperGotCollected
is called, the *internal
object itself is guaranteed still to be live because p
itself has not yet been GC-ed (it's sort of halfway there, and will be the rest of the way there as soon as, or shortly after, wrapperGotCollected
returns).
Upvotes: 3