-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: builtin: extend make()
to be a general-purpose non-nil zero value creator
#52151
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
Comments
Still thinking about the proposal but FWIW, deepZero will panic if it receives a nil interface: https://go.dev/play/p/aH7nKUIM-W_l Do this instead: https://go.dev/play/p/hcxatH0Nkfy |
Can you expand a bit on the case where this arises? If I understand correctly, for a pointer type Separately, what should |
Ah, that's the same issue that I worried about in #50741. Whoops. Thanks. Edit: I think the The specific case has something that is dynamically managing several objects for the user. A given instance of the type always deals with the same type of object, so it can be generecized, improving the ergonomics of the functions that the user provides to which the objects are passed. In other words, it's something like type Object interface { ... }
type Manager[T Object] struct {
Create func() T
Modify func(T)
}
func (m *Manager[T]) register(obj T) { ... }
func (m *Manager[T]) fromThirdParty(tp thirdparty.Type) T { ... }
func (m *Manager[T]) Manage() {
thirdparty.DoSomething(
func() thirdparty.Type {
t := m.Create()
m.register(t)
return t.ThirdParty()
},
func(tp thirdparty.Type) {
m.Modify(m.fromThirdParty(tp))
},
)
} However, c := func() T { return make(T) }
if m.Create != nil {
c = m.Create
}
t := c()
return t.ThirdParty() As for |
It seems hard to justify the fact that Therefore, this is a likely decline. Leaving open for four weeks for final comments. -- for @golang/proposal-review |
No further comments. |
Problem
Currently in Go,
make()
serves as a general instantiator for the more complex of the builtin data structures. If you want to allocate a pointer to a zero-value, on the other hand,new()
is the function to use. However, while both of these functions take a type as the first argument, they do so quite differently.new(T)
results in a non-nil*T
, butmake(T)
results in a non-nilT
directly. In other words, the type passed tomake()
is the type that it returns, whilenew()
returns a pointer to the type that it is passed instead.In most situations, this difference is not a particular issue, but with the introduction of generics it can cause some minor problems. For example, I recently wanted to be able to instantiate zero-values of a type that I was passed. The function, slimmed down into a contrived variant, is having trouble trying to do the following:
In the case of the first call to
Example()
, I could easily just create an instance ofT
by doingvar t T; return t
, but that doesn't work for the second as it will returnnil
. Similarly, I can't actually usenew(T)
as doing so creates a pointer to the type that it is passed, so it will try to return*ImplOne
for the first call and**ImplTwo
for the second, which is worthless. Worse, while the first case can be manually dereferenced after the call tonew(T)
, the second will result in returning anil
pointer, putting the whole thing back at square one.I wound up solving the issue by writing the following function:
I'm not thrilled with the need to use
reflect
for this solution, but it works.Proposal
I propose to extend the
make()
builtin to return a non-nil zero value for non-interface types, excludingunsafe.Pointer
and function types. In other words, for non-nillable types, it will simply return their regular zero value. For nillable types, on the other hand, it will return an empty, non-nil variant of whatever they represent. It already does this for slices, maps, and channels, so this proposal essentially just extends that behavior to most other concrete type.To allow for the usage of this new behavior with generics, usage of
make(T)
whereT
is anunsafe.Pointer
, function type, or interface would returnnil
instead of being a compile-time error. Alternatively, this could be made to panic. This would mean that some code would be unsafe despite compiling, but it would also leave the door open for the behavior to be changed later on without breaking the compatibility guarantee.This would let the above function be rewritten as
Concerns
One issue that I have with this is that it adds yet another way to instantiate pointers. For structs, there's already an overlap between
new(T)
and&T{}
. I think that the benefit is worth it, though.I'm also not thrilled with either solution to the issue of
unsafe.Pointer
, function types, and interfaces. I think that returningnil
is probably fine, but that would mean that it would be stuck that way. I don't like the idea of it panicking, but plenty of other simple operations do that, too, such slice-to-array conversions. Those are easier to check for safety in advance at runtime, however.The text was updated successfully, but these errors were encountered: