-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: struct field methods, or anonymous callback helper types #56647
Comments
Do you have any real-world examples of this pattern? I've never seen it, so it's difficult for me to imagine where this helps. I do think a slightly different formalism would clarify this proposal. When you say that |
Thanks for your comments. I haven't and probably won't have time to do an extensive analysis of large existing Go codebases to quantify how common this kind of need is. For the moment I'm just subjectively reporting that I've encountered this kind of need many times in a variety of contexts in recent years. Everyone's experience will vary, of course. I agree that the "two types at once" perspective might be confusing and unnecessary. I'm not sure that I immediately understand your alternative "scoped to the writer field" formulation, though. Actually, another way of thinking about this that might be conceptually simpler and cleaner is as follows. The fields of a struct still have only one type - the declared type - so the proposal changes nothing in that regard. Assignments to/from that field, and other operations using that field, still work as always. There are only two (afaik) small "point changes" in behavior:
If you take a pointer to a field with methods and assign it to a pointer variable referring to the field's underlying type, however (e.g., A more subtle question is whether that "lost information" should be dynamically recoverable or not dynamically. Maybe it's simpler to say no: if the information about the existence of the field methods is not "captured" in an immediate assignment to a suitable interface, it's lost in any resulting pointer and cannot be recovered by dynamic type casts or type switch statements. But in the "hidden helper type" implementation suggested above, the natural answer might be yes, since a dynamic cast or type switch might be able to detect that hidden type |
Another approach to this issue might be #25860. Then This proposal seems like a lot of machinery to accomplish something that we can already do, that thing being to provide an exported method on a type that is only accessible from within the type. It took us a while to understand what is happening, which suggests that the operation is rather complex for a language like Go. Is there anything we are missing here? -- for @golang/proposal-review |
Also #47487 |
Based on the discussion above, and the emoji voting, this is a likely decline. Leaving open for four weeks for final comments. |
No further comments. |
While implementing the main functionality of a Go struct, we often need to define associated "helper" types whose purpose is merely to implement a certain interface with methods that often call right back into the main struct (or directly access the state in the main struct). It would be nice if such "callback helper" types could be anonymous, avoid diverting the developer's attention away from the main struct, and avoid needing explicit back pointers to the main struct. This goal could be accomplished by allowing method declarations attached to fields within structs, as discussed below.
For example, suppose the main struct needs to invoke some standard encoding API like
json.NewEncoder()
but needs to pass anio.Writer
to it. The main struct does not want to be anio.Writer
itself: that is, it does not want aWrite()
method in its own public API. Instead, the main struct just needs a some subsidiary helper object to pass tojson.NewEncoder()
, which implements theio.Writer
interface, and whose customWrite()
method calls back into the main struct to be implemented there with access to all the state in the main struct.Concretely, I find that more-or-less the following pattern occurs often in Go code:
Of course
mainObjectWriterCallback.Write()
could simply "do the work itself" by following itsmowc.backPointer
back to theMainObject
. But this organization breaks the flow of the logic that really belongs back inMainObject
, not in themainObjectWriterCallback
helper. And the separate named typemainObjectWriterCallback
is still needed, with its explicit back pointermowc.backPointer
to the main object, which needs to be initialized explicitly inInit()
, etc.Simpler, and more concise, anonymous helper types
I propose that a future version of Go allow such helper type(s) and associated callback forwarding methods to be replaced with code like the following, in which method declarations attach can attach a method directly to a field within a struct:
Note that the
writer
field is declared as an empty, anonymousstruct{}
in this example. However, attaching method(s) to it likewriter.Write()
effectively creates an anonymous (sub-)struct type associated with that field, with the associated methods, and which is capable of satisfying any interfaces that might require those methods. Thus, the call toEncodeTo(&m.writer)
works because thewriter
field has a suitableWrite()
method, even though the field's declared type ofstruct{}
does not. Further, thiswriter.Write()
method implicitly "knows" that it is part ofMainObject
, not just an associated helper type, and can directly access all of the main struct's state via the primarym
pointer, without needing to follow any explicit back pointer from a separate helper type.Extending existing types with additional methods in anonymous helper types
The declared type need not always be an empty
struct{}
as in the above example, of course. Instead, the developer can pick any "base" type T and implicitly extend T with additional methods. In the following variation of the above example,MainObject
wants to implementwriter
as a standardbytes.Buffer
object, but needs to extend the helper object with an additionalWriteSpecialThing()
method thatsomeCodec.EncodeTo()
demands:Implementing field methods and anonymous helper types
A straightforward way to implement this feature would be by automatically rewriting the appropriate field(s) in the main method to have a hidden struct type, which uses the existing struct composition feature to "inherit" the field's declared type with its existing methods but then enhance it with additional methods. For example, the above example in which
MainObject.writer
extendsbytes.Buffer
would be functionally equivalent to the following code:A smarter implementation could probably eliminate the need for hidden back pointers at all. Suppose the Go compiler knows that this hidden helper type is only ever instantiated within a
MainObject
struct (since it's a field ofMainObject
), and that field is always located at constant offset o within theMainObject
struct. Then the implementation ofwriter.Write
- pointed to by the hidden type's method dispatch table - can simply take the pointer it receives toMainObject.writer
and subtract offset o to recover theMainObject
pointer that the code in that method needs. (This hidden pointer arithmetic happens to be equivalent to what C++ compilers already do to handle multiple inheritance efficiently, but this proposal is not about multiple inheritance.)Thus, besides the convenience and conciseness benefits of field methods, there are also potential (slight) storage space and efficiency benefits in not having to maintain (or follow) back pointers from helper types associated with a struct type.
Author background
Related proposals
Proposal
struct
that needs helper types just to implement callback interfaces that some external API demands.func (m *MainObject) writer.Write()
, whereas traditionally methods can be declared only associated with the "top-level" struct being defined.Costs
The text was updated successfully, but these errors were encountered: