Skip to content
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: builtin/new: giving new the ability to call init when creating custom types #62240

Closed
godcong opened this issue Aug 23, 2023 · 12 comments
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal Proposal-FinalCommentPeriod v2 An incompatible library change
Milestone

Comments

@godcong
Copy link

godcong commented Aug 23, 2023

In most cases, we now have users creating a user-defined type in the following way.
You can see that the person who defined the type didn't want the value to be externally modified.
But in many cases, the user may create the type in the wrong way, as in the case of //wrong
and new is used very, very rarely in the creation of user-defined types.

type NewStruct struct {
	value *string
}

func (s *NewStruct) GetValue() *string {
	return s.value
}

func New() *NewStruct {
	e := "example"
	return &NewStruct{value: &e}
}

// main()
func main() {
	//right
	s := New()
	s.GetValue()
	//wrong
	s := &NewStruct{}
	s.GetValue()
}

Based on the above, I think we should enhance the functionality of new to make it true to its name.
Just as importing a package executes init, I think that when you create a struct, you should also execute the corresponding init method (if any).
It gives the struct the correct way to initialize, and allows the developer to correctly initialize some non-public values.
It also saves the developer from having to write a New function (which would become unnecessary).
It looks like this

type NewStruct2 struct {
	value *string
}

func (s *NewStruct2) init() {
	e := "example"
	s.value = &e
}

func (s *NewStruct2) GetValue() *string {
	return s.value
}

func main() {
	s := new(NewStruct)  // new will call method `init` when create NewStruct
	s.GetValue()
}

This allows any user to use new to create types correctly.

and it makes the behavior of go more consistent...
Unreasonable questions like the following can also be answered:
Why does import execute init but not call init anywhere else, shouldn't init be called for all initializations?


2023/09/05 Add

I envision that he should work correctly like import->init().
When I create a type using new(), it try to call the method init().
The init is an internal function, so it takes care of things that only the creator wants to have to do when the function is initialized.

@gopherbot gopherbot added this to the Proposal milestone Aug 23, 2023
@godcong godcong changed the title proposal: builtin/new: giving new the ability to call init when creating structs proposal: builtin/new: giving new the ability to call init when creating custom types Aug 23, 2023
@ianlancetaylor ianlancetaylor added LanguageChange Suggested changes to the Go language v2 An incompatible library change labels Aug 23, 2023
@ianlancetaylor
Copy link
Member

For language change proposals, please fill out the template at https://go.googlesource.com/proposal/+/refs/heads/master/go2-language-changes.md .

When you are done, please reply to the issue with @gopherbot please remove label WaitingForInfo.

Thanks!

@ianlancetaylor ianlancetaylor added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Aug 23, 2023
@apparentlymart
Copy link

apparentlymart commented Aug 23, 2023

Hi @godcong,

I do largely agree with and empathize with your problem statement: I've also seen developers try to instantiate types using composite literals instead of calling a provided "New" function, and then have strange things happen when they try to use the partially-initialized value. It's frustrating that the compiler cannot give direct feedback about such mistakes.

I think your specific proposal has a challenge, though: it's already valid to declare a method named init on any named type, and so retroactively adding implicit calls to that method might be a breaking change for existing code that wasn't written to expect this convention.

Unless we can find some way to show that it isn't a breaking change after all, I suspect the details of the proposed solution would need to change so that it's proposing to add something new to the language, rather than changing the meaning of something that's already valid.


Edit: A further concern that occurred to me after further consideration is that if there is an existing method called init on a named type in code today then it's currently unexported and thus not directly callable from outside of the package where it's declared.

Making that function callable from outside of the package indirectly using new would therefore potentially break that type's intended abstraction.

@seankhliao
Copy link
Member

see previously #28939

@Merovius
Copy link
Contributor

Merovius commented Aug 23, 2023

Note that it is fundamentally impossible to prevent a user from creating a zero value of your type. For example, you can write

var x T

s := make([]T, 1)
x := s[0]

m := map[bool]T{}
x := m[true]

ch := make(chan T)
close(ch)
x, _ := <-ch

var s []T
for cap(s) == len(s) {
    s = append(s, *new(T))
}
x := s[:cap(s)][cap(s)-1]

or any number of other ways. The property of any Go type having a zero value - and that zero value being represented entirely by 0 bits in memory - is a deliberate choice, deeply ingrained and assumed in the language. It's relied on for efficiency in a number of places as well (the examples above demonstrate how make an append make that assumption to be efficient).

I'll also note that while NewT can take arbitrary arguments, your init function can't (and it can't be made to do that, given that e.g. append most definitely can't take any constructor arguments). That means that we'd still have an unfortunate asymmetry where some types - even without a zero value - can be initialized using new, while others can't.

I really don't see this happening, to be honest.

@godcong
Copy link
Author

godcong commented Aug 28, 2023

Proposal Title

Automatic Invocation of User-Defined init Method when Using new

Summary

Automatically invoke the init method of a user-defined type when creating an instance using the new keyword.

Motivation

In the current version of Go language, when creating an instance of a user-defined type using the new keyword, the init method of that type is not automatically invoked.
This requires developers to manually call the custom NewFunction to initialize the instance. To improve development efficiency and code readability, it is proposed to automatically invoke the init method of user-defined types when creating instances using the new keyword.

Detailed Design

When creating an instance of a user-defined type using the new keyword, the compiler will automatically invoke the init method of that type immediately after the instance is created. This ensures that the instance is properly initialized right after creation without the need for manual invocation of the init method by developers.

Here is an example code demonstrating the usage of this feature:

package main

import "fmt"

type MyStruct struct {
    Name string
}

func (m *MyStruct) init() {
    m.Name = "Initialized"
}

func main() {
    instance := new(MyStruct)
    fmt.Println(instance.Name) // Output: Initialized
}

Compatibility

The introduction of this language feature will not affect existing Go code, as it is an enhancement specifically for instances created using the new keyword. Existing code will continue to work as before.

Open Questions

The introduction of this language feature may raise some discussions and questions, such as:

  • Should the init method support parameter passing?
  • Should the init method support return values?
  • Should the init method be allowed to be called multiple times?

These questions will require further research and decision-making during the discussion and implementation process.
@gopherbot please remove label WaitingForInfo

@gopherbot gopherbot removed the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Aug 28, 2023
@godcong
Copy link
Author

godcong commented Aug 28, 2023

@godcong In short, what u r proposing is a struct).constructor' for all types(not just `

I voting down for this proposal-

  1. Strictly Backwards incompatible
  2. Overloading of new
  3. Options pattern offers better flexibility, control and readability over constructors
    @Nasfame
    Did you misunderstand me?

When new has finished what it was doing, it calls init().
If you don't implement init(), you don't have to do anything.
All the actions are the same as before, it's not overloading, and it doesn't affect the original processing.

@ianlancetaylor
Copy link
Member

It's simple to write a factory function that does exactly what you want. This proposal doesn't add new functionality that we don't have already, but introduces a number of difficulties as @Merovius mentions above. Also, the emoji voting is not in favor. Therefore, this is a likely decline. Leaving open for three weeks for final comments.

@ianlancetaylor
Copy link
Member

No further comments.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Oct 4, 2023
@golang golang locked and limited conversation to collaborators Oct 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal Proposal-FinalCommentPeriod v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests

7 participants
@apparentlymart @Merovius @godcong @ianlancetaylor @gopherbot @seankhliao and others