Skip to content

c: ClosureData, GoClosure - #28

Merged
xushiwei merged 1 commit into
goplus:mainfrom
xushiwei:q
Sep 6, 2026
Merged

xushiwei merged 1 commit into
goplus:mainfrom
xushiwei:q

Conversation

@xushiwei

@xushiwei xushiwei commented Sep 6, 2026

Copy link
Copy Markdown
Member

No description provided.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR adds a symmetric closure round-trip pair — ClosureData[T] (heap-allocates a closure and returns an opaque Pointer for passing across the C boundary) and GoClosure[T] (reconstructs the closure from that Pointer). The core logic is correct, minimal, and follows the existing GoString/GoStringData naming convention in this file.

The only actionable items are documentation of the non-obvious unsafe contract. These are appropriate for a low-level llgo runtime binding but should be spelled out for callers, since the invariants (matching type parameter, GC lifetime, non-nil data) are invisible in the signatures and unchecked at runtime.

No correctness-blocking issues found.

Comment thread c/c.go

// -----------------------------------------------------------------------------

func ClosureData[ClosureT any](closure ClosureT) Pointer {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Document ClosureData's unsafe contract (GC lifetime + exported doc comment)

ClosureData is an exported function with no doc comment, unlike neighbors such as GoString (line 180). Beyond golint/godoc convention, the lifetime contract is non-obvious: new(ClosureT) allocates a Go-managed object returned as a bare unsafe.Pointer. Once handed to C, the GC no longer sees a typed reference, so if C retains the pointer past the last Go reference the backing object can be collected or moved, leaving a dangling pointer. Recommend a doc comment stating (1) the purpose and (2) that the caller must keep the returned Pointer's backing allocation reachable (e.g. via runtime.KeepAlive/runtime.Pinner) for as long as C uses it.

Comment thread c/c.go
return Pointer(ret)
}

func GoClosure[ClosureT any](data Pointer) (closure ClosureT) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Document GoClosure's unchecked type reinterpretation + nil precondition

GoClosure is exported with no doc comment and performs *(*ClosureT)(data), blindly reinterpreting data as *ClosureT. Because ClosureT is inferred independently at each call site and is invisible in the raw Pointer, a mismatch between the type used at ClosureData and here is silent memory corruption with no compiler/runtime guard. Additionally, a nil data (common for a C callback's opaque user-data slot) dereferences nil and crashes. Recommend a doc comment stating that data must originate from ClosureData with the same ClosureT and must be non-nil (and/or add an early nil guard).

@xushiwei
xushiwei merged commit bbaa5ad into goplus:main Sep 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant