The short answer is no, this won't work reliably, for several reasons:
1. Race between move and goroutine creation
go func(c2 *ftp.ServerConn) {
c2.List()
}(move(&c))
The sequence is:
1. move(&c) executes in G1
2. Return value sits in G1's registers/stack
3. New goroutine G2 is scheduled
4. Value is copied to G2's stack
Between steps 1-4, the pointer lives in G1's execution context. The GC
could run during this window.
2. Compiler doesn't understand ownership
c, err := getFtpConnection()
// Compiler may keep 'c' in a register here
go func(c2 *ftp.ServerConn) {
c2.List()
}(move(&c))
// Even with c = nil, compiler might have copies
Go's compiler can:
- Keep copies in registers
- Introduce temporaries you don't see
- Reorder operations
Setting c = nil in source doesn't guarantee all machine-level references
are cleared.
3. GC scans conservatively
Go's GC scans entire stack frames, including:
- All slots that could hold pointers
- Registers at safepoints
- Temporary locations
There's no mechanism to tell the GC "I've semantically transferred this
pointer."
4. The move generic doesn't compile
func move[PT *any](pp *PT) (res PT) // Won't work
You'd need:
func move[T any, PT *T](pp *PT) PT
But even then, it doesn't achieve the semantic goal.
5. What would actually be needed
For true ownership transfer, you'd need runtime support:
// Hypothetical - doesn't exist
func runtime.TransferOwnership[T any](from *T) T {
// 1. Atomically mark object as "in transfer"
// 2. Clear stack slot with write barrier
// 3. Return value with new ownership metadata
}
This would require changes to:
- GC's stack scanner (recognize transferred objects)
- Write barriers (track ownership changes)
- Escape analysis (understand cross-goroutine transfers)
The fundamental issue
Go's memory model is shared memory with GC, not ownership-based. The GC
assumes any reachable pointer might be used. There's no concept of "this
goroutine owns this object."
Rust can do this because ownership is compile-time — the borrow checker
ensures single ownership. Go would need either:
1. Compile-time ownership tracking (major language change)
2. Runtime ownership metadata (significant GC overhead)
What you can do instead
// Use channels for explicit handoff
ch := make(chan *ftp.ServerConn, 1)
c, err := getFtpConnection()
if err != nil {
return nil, err
}
ch <- c
c = nil // Now truly unreachable from G1
go func() {
c2 := <-ch
c2.List()
}()
This achieves a cleaner ownership transfer, though the GC still manages
the lifetime — it just now knows c in G1 is nil before G2 runs.
Em quarta-feira, 7 de janeiro de 2026 às 04:15:20 UTC-3, Axel Wagner
escreveu:
> I'm not sure your `move` function actually does anything in this case.
> AIUI the compiler and runtime are already clever enough to recognize that c
> is no longer used after the `go` statement (hence the necessity of
> runtime.KeepAlive) and that's all your `move` is trying to do, no?
> And in the general case, you have no guarantee that a pointer passed to
> `move` is the *only* pointer to the relevant object.
> So ISTM the cases where the Go implementation *can't* tell that c is no
> longer used, adding the `move` doesn't really help.
>
> Do you have concrete evidence that this helps in some cases? Because
> otherwise it seems like a premature optimization that mostly makes the code
> harder to read.
>
> On Wed, 7 Jan 2026 at 04:25, Qingwei Li <[email protected]> wrote:
>
>> Take the following program as an example.
>>
>> ```go
>> c, err := getFtpConnection()
>> if err != nil {
>> return nil, err
>> }
>> go func(c2 *ftp.ServerConn) {
>> c2.List()
>> }(c)
>> // c will not be used later
>> ```
>>
>> Let's add the `move` function.
>>
>> ```go
>> // pointer is
>> func move[PT *any](pp *PT) (res PT) {
>> res = *pp
>> *pp = nil
>> return
>> }
>> ```
>>
>> ```go
>> // Goroutine G1
>> c, err := getFtpConnection()
>> if err != nil {
>> return nil, err
>> }
>> go func(c2 *ftp.ServerConn) { // Goroutine G2
>> c2.List()
>> }(move(&c))
>> // c will not be used later
>> ```
>>
>> Would this `move` without runtime support make GC unable to reach the
>> object pointed by `c` scanning from the stack of goroutine G1 so that the
>> ownership of object is entirely moved to goroutine G2? With this ownership
>> transfering, freegc for `c2` in the end of goroutine G2 is feasible in
>> cross-goroutine reference case.
>>
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "golang-nuts" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to [email protected].
>> To view this discussion visit
>> https://groups.google.com/d/msgid/golang-nuts/5c0b8aaf-8470-4de2-91f6-5d74e884dff3n%40googlegroups.com
>>
>> <https://groups.google.com/d/msgid/golang-nuts/5c0b8aaf-8470-4de2-91f6-5d74e884dff3n%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>>
>
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/golang-nuts/17a2b878-c3a8-4572-bc73-51e8f12266cfn%40googlegroups.com.