It's been discussed several times before--see the list archives. No
specific solution has ever seemed completely satisfactory.
Ian
"In short: new allocates memory, make initializes the slice, map, and
channel types."
Some structs need initialization to be useful. If would be very
inconsistent if make() initialized slices,maps, and channels, but only
allocated memory for structs. There would also have to be some magic
where make() sometimes returned a pointer and sometimes returned a
value depending on what you asked it to do.
The current case is consistent with make() and new() performing distinct tasks.
--
=====================
http://jessta.id.au
I've never really understood what the problem with Go's distinguishing
make() and new() is. You use make() to create -- that is, construct values
for -- slices, channels, and maps. You use new() when you want to allocate
a pointer to a fresh copy of the zero value of the pointed-to type, and you
almost never actually need to use it. Those are two different kinds of
action.
The proposals I've seen fly past essentially all trade the bump
in the carpet that is the new/make distinction for a different bump,
bigger but more obscure.
Chris
--
Chris "not a Go designer" Dollin
a channel needs to know its buffer size.
The zero values for channels are 'usable', you can use a nil channel
in a select and it will be ignored.
It would be nice if:
map['something'] = "something"
make()'d the map if it wasn't already make()'d
--
=====================
http://jessta.id.au
For starters, it would be impossible to have a nil map/channel.
And it would be difficult to see the point of initialisation, since any
use might be that point.
Chris
--
Chris "allusive" Dollin
> It would be nice if:
> map['something'] = "something"
> make()'d the map if it wasn't already make()'d
I think you would then get complaints from people from the seqence:
var M someMapType
someFunction(M)
... look at M ...
...
func init(M' someMapType) { M'[K] = V }
where M would not be initialised.
(ignoring the past 20 minutes of rapid fire discussion)
+1 ;-) make([]T, 5) only initializes the slice, not the elements. I
don't see why make(*T) would be expected to behave any different.
Arguments against this either conflate pointers with what they are
pointing to, or are getting confused by aspects of the implementation
which aren't part of the language definition. A pointer is a reference
to a single element. A slice is a reference to multiple ordered
elements [belonging to an array]. The amount of initialization for
each of these two concepts is implementation dependent, and you cannot
dismiss that both have to be initialized by saying one is more complex
than the other.
By the same token, I'd like to see pointer composite literals
(`(*int){5}`), but acknowledge that they aren't necessary (there's
only a few cases where they'd be useful), and as such, might just be
consistency without reason (which is bad). They would be useful for
making an in-place copy to pass to a function, though.
As far as stumbling blocks go, make/new isn't all that bad compared to
`:=`, but I find the false dichotomy intellectually annoying
(obviously not a language design argument :), and we've seen that the
distinction does confuse new users... People who get past this
confusion come to accept that new *makes* a pointer to the type passed
to it. Using make for pointers would remove this problem entirely,
since the code is identical to the sentence describing it.
What I've never understood is why that distinction *requires* two
different functions/operators? Doesn't the compiler know it's dealing
with a slice/map/channel? What prevents new(slice/map/channel) from
simply doing what make(slice/map/channel) does today? Or the other way
around, of course.
> I haven't figured this out yet.
>
> These seem to do the same thing:
>
> m1 := map[string] int{}
> m2 := make(map[string] int)
>
> And so do these:
>
> p1 := new(map[string] int)
> p1 = &m1
> p2 := &m1
> p3 := &(map[string] int{})
> p3 = &m1
>
> Or are they different?
m1 and m2 should be indistinguishable.
By the time your second code block finishes, p1, p2, and p3 should all be the same (pointers to m1), but two uninitialized maps will have been allocated and left on the heap to be garbage-collected.
> When do you actually need to use new or make?
You should seldom (if ever) need to use new with a map type, because there is little need for a pointer to a map. Maps already act a lot like references without the extra indirection.
You need to use make every time you create a map. Even if for some reason you allocated a map with new, you would still need to initialize it with make before you could use it:
p4 := new(map[string]int)
*p4 = make(map[string]int)
I see. A nil slice can be used without being initialized, since it automatically (because of zeroed memory) has a length and capacity of 0. But initializing maps and channels would violate Go's philosophy of not doing things behind your back.
>
> On Jul 6, 2011, at 2:56 PM, peter wrote:
>
> > I haven't figured this out yet.
> >
> > These seem to do the same thing:
> >
> > m1 := map[string] int{}
> > m2 := make(map[string] int)
> >
> > And so do these:
> >
> > p1 := new(map[string] int)
> > p1 = &m1
> > p2 := &m1
> > p3 := &(map[string] int{})
> > p3 = &m1
> >
> > Or are they different?
>
> m1 and m2 should be indistinguishable.
>
> By the time your second code block finishes, p1, p2, and p3
> should all be the same (pointers to m1), but two uninitialized
> maps will have been allocated and left on the heap to be
> garbage-collected.
I noticed I can add items to the maps m1 and m2, and to the one
pointed to by p3, before I do p3 = &m1
That makes three initialized maps.
Where are the uninitialized maps? Or do you mean empty maps?
I can't add items to the map where p1 is pointing to before I do
p2 := &m1
Before, p2 seems to be just a pointer pointing nowhere. But,
this isn't correct? It actually points to a map, but you can't
use it, you can't put things in it. Then what is it it points
to?
But if I use new for a struct, I get a pointer to a newly
created instance of that struct, with all values set to zero.
This looks as if two different language things are used for
something that is actually just an implementation issue.
> > When do you actually need to use new or make?
>
> You should seldom (if ever) need to use new with a map type,
> because there is little need for a pointer to a map. Maps
> already act a lot like references without the extra
> indirection.
Are there other variables that require new? I can also create a
struct without new:
type t struct { a, b, c int }
tt := t{}
> You need to use make every time you create a map. Even if for
> some reason you allocated a map with new, you would still need
> to initialize it with make before you could use it:
>
> p4 := new(map[string]int)
> *p4 = make(map[string]int)
I thought make created the map. How can it initialize a map that
is already created?
This is indeed a tricky subject for a newcomer like me.
--
Peter Kleiweg
http://pkleiweg.home.xs4all.nl/
> > When do you actually need to use new or make?
> >
> well, you *need* to use make if you're creating a slice, map, or channel
> that has a specified internal size or capacity. These aren't available as a
> part of a composite literal, and the cases in which you need them are pretty
> distinct from the cases in which a composite literal is used. You also
> cannot create a *int without using new, not that you should be creating one
> anyway (again unless you know what you're doing). I should also mention
> that using new on an interface type is not common, despite looking identical
> to a new of any other named type.
I haven't studied channels yet. I understand the need to use
make for a slice or array. What can I do with make to create a
map that I can't do without? You cannot specify a capacity for a
map, can you?
> A simpler way to express it might be this: Use a composite literal when you
> know most of what the value is going to be. Use new when you really just
> need a pointer to a newly allocated object and are going to fool around with
> its value on the fly. Use make when you need an empty map/slice or channel
> or want to give it a capacity, length, or size---just like the previous
> case, you will be constructing its value on the fly.
I think I will avoid using new for the time being.
You need to use make every time you create a map. Even if for some reason you allocated a map with new, you would still need to initialize it with make before you could use it:
p4 := new(map[string]int)
*p4 = make(map[string]int)
The same questions go for slices too.
> Why would I ever need to use make for a map if I can just use 'm :=
> map[string]int{}' instead?
> Why would I ever need to use new for a map if I can just use 'p :=
> &map[string]int{}' instead?
> The same questions go for slices too.
> It seems to me that the only reason to use make/new is when you want to
> create a channel. This is because, as far as I am aware, channels cannot be
> created as a literal.
make can take arguments to set a map's capacity, or set a slice's
length/capacity. You can't do that with a literal.
Dave.
'New' is not some arbitrary piece of cruft you can ignore when it bothers you. It's the memory allocator.
I suggest using 'make' when you need to create a channel, slice, or map, and 'new' when you need to allocate new storage for a zero value of an item.
The composite literal syntax (&x{a,b,c}) is a convenience hack; under the covers it still calls 'new', and it only applies to composites (hence the name).
I get the feeling some people in this discussion do not appreciate the differences between pointers and values, or between zero values and initialized data structures. To use Go well, you must know how memory and addressing works.
-rob
I get the feeling some people in this discussion do not appreciate the differences between pointers and values, or between zero values and initialized data structures. To use Go well, you must know how memory and addressing works.
I haven't studied channels yet. I understand the need to use
make for a slice or array. What can I do with make to create a
map that I can't do without? You cannot specify a capacity for a
map, can you?
> A simpler way to express it might be this: Use a composite literal when youI think I will avoid using new for the time being.
> know most of what the value is going to be. Use new when you really just
> need a pointer to a newly allocated object and are going to fool around with
> its value on the fly. Use make when you need an empty map/slice or channel
> or want to give it a capacity, length, or size---just like the previous
> case, you will be constructing its value on the fly.
>> On Jul 6, 2011, at 2:56 PM, peter wrote:
>>
>> > I haven't figured this out yet.
>> >
>> > These seem to do the same thing:
>> >
>> > m1 := map[string] int{}
>> > m2 := make(map[string] int)
>> >
>> > And so do these:
>> >
>> > p1 := new(map[string] int)
>> > p1 = &m1
>> > p2 := &m1
>> > p3 := &(map[string] int{})
>> > p3 = &m1
>> >
...
> I can't add items to the map where p1 is pointing to before I do
> p2 := &m1
>
> Before, p2 seems to be just a pointer pointing nowhere. But,
> this isn't correct? It actually points to a map, but you can't
> use it, you can't put things in it. Then what is it it points
> to?
I'm not sure whether you mean p2 here. In general, if you have a
pointer to a map, and the pointer is not initialized, then it doesn't
point to anything. The uninitialized valuee for a pointer is nil.
> But if I use new for a struct, I get a pointer to a newly
> created instance of that struct, with all values set to zero.
Yes.
> This looks as if two different language things are used for
> something that is actually just an implementation issue.
In Go, a pointer can point to something, or it can be nil. An
uninitialized pointer is nil. I don't consider this to be an
implementation issue.
> Are there other variables that require new? I can also create a
> struct without new:
>
> type t struct { a, b, c int }
> tt := t{}
No types require "new". You would use "new" when you want to get a
pointer to an uninitialized value of some type. The code here is not
creating a pointer; if you wrote &t{}, it would create a pointer. An
example of using new would be something like
p := new(int)
>> You need to use make every time you create a map. Even if for
>> some reason you allocated a map with new, you would still need
>> to initialize it with make before you could use it:
>>
>> p4 := new(map[string]int)
>> *p4 = make(map[string]int)
>
> I thought make created the map. How can it initialize a map that
> is already created?
The new function does not create a map. It creates an uninitialized
value of a given type, and returns a pointer to it. The uninitialized
value for a map type is nil. If you want to add something to the map,
you need to give it a value; you can do that using make.
Ian
Go is the same, except that '->' is replaced by '.' and you use new
rather than malloc.
var s T
var sp *T
sp = new(T)
s.a = 1
sp.a = 1
Ian
Key word is "seems". Pointers and values behave in the same way in Go,
and Go also passes parameters by value, with the same consequences as
in C. There's no additional complexity here; the principle difference
is that . is used for members of both values and pointers, and method
receivers exist (but behave exactly like parameters do).
The complex part is initialisation, which is also tricky in C but not a
problem the language tries to solve.
new() performs no initialisation, returning a pointer to zeroed memory.
This is the usual way to create most types, built-in and user-defined,
on the heap.
make() performs initialisation of, and returns a value for one
of the built-in types that require initialisation.
pkg.NewSomething() functions are usually the way to provide
initialisation for a user-defined type, but unlike make(), often
returns a pointer.
I don't personally think there's anything wrong with this. Knowing
whether you need to use new() or initialise the type is a type-specific
thing you need to know for all types you use, but I've not seen any
solutions that extend beyond the built-in types, which are the least
problematic, as anyone who's used Go for any length of time can
remember them.
I think it would be more inconsistent for built-in types to be exempted
from this issue, technically speaking.
As for the blog post... it does not appear to be using a standard
meaning of inconsistency, instead substituting "surprised me", which I
think is a little presumptious, as well as wrong, as well as claiming
that he has to know "implementation details", which is also wrong.
Knowing you need to know whether types require initialisation is a tad
tricky at first, but is something he should have tried harder to
understand before blaming the language and throwing around unpleasant
words with disregard for their meaning.
The part on pointers seems to be speculative, as I cannot see the
proposed scenario in which the user doesn't know what the methods he is
calling do, even whether they modify the type at all, actually
occurring; regardless, if it somehow did, a glance at the signature
would tell him if it could modify the type. For any sizeable struct it's
obviously better to just pass around pointers to begin with.
At any rate, I don't feel the accusations of "inconsistency" hold any
strength. Reddit users may have harsh comments, but the opinions of
news site commenters aren't particularly worth caring about.
Go has precise memory layout and it has pointers. Given that, what is
the value of an uninitialized pointer?
Personally, I view having an uninitialized value of map type be nil as
basically an efficiency hack. It lets us assume that that all
uninitialized variables can be zero. If an uninitialized map were not
nil--if creating a map did not require a call to make or the use of a
composite literal--then I don't see any way to retain the simplicity and
efficiency of having uninitialized values be zero.
Ian
This is where I think `new(T)` is creating some harm. You can do `t :=
new(T)` and ignore that t isn't a T but is rather a *T. Obviously,
this isn't a problem for people who grok pointers, but for people who
are new to them, it gives them an avenue to ignore them, which just
creates problems. The benefit of `make(*T)` is that it forces people
to think of pointers as a distinct kind of type, like slices, maps,
and channels, rather than glossing over them as a quirky thing about
value types. I also don't think the parallel people draw to the Java
keyword "new" is all that helpful if they don't realize that Java
objects are really pointers.
I noticed I can add items to the maps m1 and m2, and to the one
pointed to by p3, before I do p3 = &m1
That makes three initialized maps.Where are the uninitialized maps? Or do you mean empty maps?
These seem to do the same thing:
m1 := map[string] int{}
m2 := make(map[string] int)
And so do these:
p1 := new(map[string] int)
p1 = &m1
p2 := &m1
p3 := &(map[string] int{})
p3 = &m1
Or are they different?
When do you actually need to use new or make?
Couldn't there be a polymorphic built-in that looks at the type of
argument to correctly initialize the object? eg.
type Foo struct {
slice map[string]*VeryLongTypeName
}
func NewFoo() *Foo {
f := &Foo{}
make(&f.slice) // shorter than: make(map[string]*VeryLongTypeName)
return f
}
--
Han-Wen Nienhuys
Google Engineering Belo Horizonte
han...@google.com
Personally the only thing that irks me with make() is having to write
the type again when initializing structs,.
Couldn't there be a polymorphic built-in that looks at the type of
argument to correctly initialize the object? eg.
type Foo struct {
slice map[string]*VeryLongTypeName
}
func NewFoo() *Foo {
f := &Foo{}
make(&f.slice) // shorter than: make(map[string]*VeryLongTypeName)
return f
}
> Simply: new(T) returns *T, while make(T) returns T. That is all that
> is to know here.
No: new(T) returns a *T pointing to a zeroed T, while make(T) returns an initialized (not zeroed) T. In other words, new allocates; make initializes.
If you come from a language where new initializes, this can confuse you. If you come from a language where everything is an object, this can confuse you. If you come from a language where everything is a pointer but you pretend it's not, this can confuse you.
There are lots of languages with those properties. Go isn't one of them; instead Go reflects the nature of the underlying machine model.
-rob
Certainly this is a true statement. The question is whether it is the
best way to define things. A few notes on my part:
1) The EffectiveGo documentation recommends that you write initializers
as: NewMyType() *MyType
It explicitly uses:
func NewFile(fd int, name string) *File
And *all* of these "New" functions are returning initialized types. So
there is at least some confusion because:
new(MyType) is uninitialized but
NewMyType() is initialized
If new/make distinction is to be followed, it would make a lot more
sense to have:
MakeMyType() *MyType
2) 'make' can only be used for Go-defined types, and I don't think all
of them at that. (you can't "make(string, 10)" for example, though
partially that is because strings are immutable.)
But it does mean that I can't define some-special-sauce and get:
make(MyType)
return an initialized version of MyType.
This shows up in other areas, like "range" not working on things that
aren't built-in types.
3) Go seems to be moving away from obviously defined storage. In that
saying:
foo := MyType{}
Means it is allocated on the stack, unless you then do:
bar := &foo
At which point the previous 'foo' declaration now is allocated on the heap.
If the language wants to hide whether something is on the stack or on
the heap, then why do we need "new" at all? (What use cases of 'new'
aren't covered by &MyType{}. It is even fewer characters to type than
new(MyType).)
4) new/make gets more confusing after type declarations. It is pretty
obvious if you see:
x := make(map[string]string)
x := new(map[string]string) # is "obviously" wrong
It is no longer obvious after a type declaration, especially at a distance:
type Dict map[string]string
x := Dict{}
x := make(Dict)
x := new(Dict)
5) I think "deprecating" new and looking at a way to have make
initialize user-defined types would be nice. (especially as that like
leads the way to have 'range' work on more types, etc.) "magic" built-in
objects that have different semantics than all other objects tend to
chafe a bit. It may be a necessity, but thing like Python's magic
members (__init__, __len__, __getitem__) do add complexity, but avoid
the specialization, I think.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iEYEARECAAYFAk4VkdAACgkQJdeBCYSNAAMLhgCcCR8yyO61CWK/MqFoXYeHTYiq
1JYAoM+OrtgGV0E2volCJYWp+ThbW3OG
=ia0r
-----END PGP SIGNATURE-----
It [the pointed-to storage] /is/ initialised, to a zero value.
> NewMyType() is initialized
to something as its documentation specifies, same as for
new().
> If new/make distinction is to be followed, it would make a lot more
> sense to have:
> MakeMyType() *MyType
Maybe. Now were just talking naming conventions.
> 2) 'make' can only be used for Go-defined types,
In fact only for chanels, maps, and slices. That's what it's
there for: to do things that can't otherwise be done, to be
the base case for non-zero values for those types.
> But it does mean that I can't define some-special-sauce and get:
> make(MyType)
> return an initialized version of MyType.
You'll note that the language has /no/ built-in methods; which
is why:
> This shows up in other areas, like "range" not working on things that
> aren't built-in types.
> 3) Go seems to be moving away from obviously defined storage. In that
> saying:
>
> foo := MyType{}
>
> Means it is allocated on the stack, unless you then do:
> bar := &foo
>
> At which point the previous 'foo' declaration now is allocated on the heap.
Variables are allocated on the heap unless the compiler can show
that its correct to allocate them in the stack, which it can do
if their address is not taken (implicitly or explicitly). There's
no "at which point" in your example: foo is allocated on the heap
because its address is taken (and the compiler isn't smart
enough at present to see if, say, that address never leaks out
of the function its taken in).
> If the language wants to hide whether something is on the stack or on
> the heap, then why do we need "new" at all? (What use cases of 'new'
> aren't covered by &MyType{}.
ints, floats, complexes, strings.
> 4) new/make gets more confusing after type declarations. It is pretty
> obvious if you see:
>
> x := make(map[string]string)
>
> x := new(map[string]string) # is "obviously" wrong
Not in the least. It allocates a pointer to a zero-valued map.
That's a perfectly sensible thing to do.
> It is no longer obvious after a type declaration, especially at a distance:
> type Dict map[string]string
>
> x := Dict{}
> x := make(Dict)
> x := new(Dict)
I don't see the problem. All of those things make sense.
Knowing which one does what you want is a matter of what
you're trying to do.
Rather than trying to reconstruct this corner of the language,
I wonder if we can find a form of words for Effective Go and
the tutorial which is more effective at avoiding people
garden-pathing down misunderstandings. I'm probably
to close to the problem to see why it's a problem ...
Chris
--
Chris "allusive" Dollin
1) The EffectiveGo documentation recommends that you write initializers
as: NewMyType() *MyType
It explicitly uses:
func NewFile(fd int, name string) *FileAnd *all* of these "New" functions are returning initialized types. So
there is at least some confusion because:new(MyType) is uninitialized but
NewMyType() is initialized
If new/make distinction is to be followed, it would make a lot more
sense to have:
MakeMyType() *MyType
2) 'make' can only be used for Go-defined types, and I don't think all
of them at that.
(you can't "make(string, 10)" for example, though
partially that is because strings are immutable.)
But it does mean that I can't define some-special-sauce and get:
make(MyType)
return an initialized version of MyType.This shows up in other areas, like "range" not working on things that
aren't built-in types.
3) Go seems to be moving away from obviously defined storage. In that
saying:foo := MyType{}
Means it is allocated on the stack, unless you then do:
bar := &foo
At which point the previous 'foo' declaration now is allocated on the heap.
If the language wants to hide whether something is on the stack or on
the heap, then why do we need "new" at all? (What use cases of 'new'
aren't covered by &MyType{}. It is even fewer characters to type than
new(MyType).)
4) new/make gets more confusing after type declarations. It is pretty
obvious if you see:x := make(map[string]string)
x := new(map[string]string) # is "obviously" wrong
It is no longer obvious after a type declaration, especially at a distance:
type Dict map[string]string
x := Dict{}
x := make(Dict)
x := new(Dict)
5) I think "deprecating" new and looking at a way to have make
initialize user-defined types would be nice. (especially as that like
leads the way to have 'range' work on more types, etc.) "magic" built-in
objects that have different semantics than all other objects tend to
chafe a bit. It may be a necessity, but thing like Python's magic
members (__init__, __len__, __getitem__) do add complexity, but avoid
the specialization, I think.
And *all* of these "New" functions are returning initialized types. So
there is at least some confusion because:new(MyType) is uninitialized but
NewMyType() is initialized
2) 'make' can only be used for Go-defined types, and I don't think all
of them at that. (you can't "make(string, 10)" for example, though
partially that is because strings are immutable.)
But it does mean that I can't define some-special-sauce and get:
make(MyType)
return an initialized version of MyType.
This shows up in other areas, like "range" not working on things that
aren't built-in types.
3) Go seems to be moving away from obviously defined storage. In that
saying:foo := MyType{}
Means it is allocated on the stack, unless you then do:
bar := &fooAt which point the previous 'foo' declaration now is allocated on the heap.
If the language wants to hide whether something is on the stack or on
the heap,
4) new/make gets more confusing after type declarations.
5) I think "deprecating" new and looking at a way to have make
initialize user-defined types would be nice. (especially as that like
leads the way to have 'range' work on more types, etc.) "magic" built-in
objects that have different semantics than all other objects tend to
chafe a bit.
...
>> 5) I think "deprecating" new and looking at a way to have make
>> initialize user-defined types would be nice. (especially as that like
>> leads the way to have 'range' work on more types, etc.) "magic" built-in
>> objects that have different semantics than all other objects tend to
>> chafe a bit.
>>
> But these types _are_ special and they _do_ have different semantics. Why
> hide the reality? To me, _that_ would be confusing and inconsistent.
>
> Andrew
>
But why do they *need* to be different and have different semantics.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iEYEARECAAYFAk4VmL0ACgkQJdeBCYSNAAPqcACgyKKchnSPx0ofLl6ScJJtLDpZ
qucAn3btejSj+cXYbru2U8Aj+kic18Oj
=rGLx
-----END PGP SIGNATURE-----
> 'range' works with "array, pointer to an array, slice, string, map, or
> channel.", i.e. also on user defined types.
Not on structs, not on pointers to structs, not on typenamed ints or
strings or whatever. So if one were to create ones own container
type, one couldn't have range work on it (to iterate over all the
elements) without having to introduce channels & goroutines.
This argument has arisen many times and I fully disagree with it.
Even "at a distance" you *must* know what is the type of Dict (you are
using it, after all!), and knowing its type it is fairly obvious which
to use.
Even if there is only "new" or only "make" you must know the type of
Dict to know what you are getting from new/make (initialization?, just
allocation?).
For me, having both makes the code simpler and more readable. If I see:
x := make(Dict)
I know that Dict is a slice, map or channel.
M;
For me, having both makes the code simpler and more readable. If I see:
x := make(Dict)
I know that Dict is a slice, map or channel.
Anybody denying that the above paragraphs are the accurate description
of the primary root of the confusion is wrong. The cause of the
confusion is in Go, not outside of Go - unlike some Go authors seem be
thinking.
I'm pretty sure that:
x := new(map[string]string]
x["foo"] = "bar"
Would have different effects if an implementation used a value rather
than a reference. (In reference version, it fails because you haven't
allocated the actual underlying object.)
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iEYEARECAAYFAk4VoOQACgkQJdeBCYSNAAPr4ACeP3CAzKdhQalD/v72LJDtZAi+
zy4AniOLV7DDX7lJXwX+ZNEJkr+bknhV
=LqcT
-----END PGP SIGNATURE-----
...
>> 1) The EffectiveGo documentation recommends that you write initializers
>> as: NewMyType() *MyType
>> It explicitly uses:
>> func NewFile(fd int, name string) *File
>>
>> And *all* of these "New" functions are returning initialized types. So
>> there is at least some confusion because:
>>
>> new(MyType) is uninitialized but
>
> It [the pointed-to storage] /is/ initialised, to a zero value.
>
I'm going of Rob's explicit statement: "No: new(T) returns a *T pointing
to a zeroed T, while make(T) returns an initialized (not zeroed) T. In
other words, new allocates; make initializes."
You can argue that the 0 value is initialization or not. But the fact
that people have different ideas about what it means is why people
struggle with it. If you want the meme "new allocates, make initializes"
then it should be reflected in the Tutorial and the standard library.
I'm sure Rob doesn't speak for all developers. But it sure would be nice
if there was an official nomenclature for talking about these things.
>> NewMyType() is initialized
>
> to something as its documentation specifies, same as for
> new().
>
Saying that X does Y because it is documented to do so is a valid
statement. However, if I write this:
// Delete the file on disk
func Open(path string) os.Error {
}
If Open then deletes the file on disk, it is at least "surprising" if
not "inconsistent" with the naming of the function.
Having:
type MyType(map[string]string)
n := new(MyType)
vs
n := NewMyType()
vs
n := make(MyType)
They all do something which might be useful for some edge case. I at
least posit that the value isn't enough to warrant the confusion.
...
>> 2) 'make' can only be used for Go-defined types,
>
> In fact only for chanels, maps, and slices. That's what it's
> there for: to do things that can't otherwise be done, to be
> the base case for non-zero values for those types.
Sure. The open question is still "why can't it be done?". Why do you
have to have types which are language-special that user-defined types
cannot emulate.
>
>> But it does mean that I can't define some-special-sauce and get:
>> make(MyType)
>> return an initialized version of MyType.
>
> You'll note that the language has /no/ built-in methods; which
> is why:
>
>> This shows up in other areas, like "range" not working on things that
>> aren't built-in types.
>
I'm not sure what you mean by "/no/" built-in methods. range, len,
append, copy, cap are functions that come with the language (and cannot
be easily copied, if only because of type restrictions). Maps and slices
have [] syntax which also cannot be copied. Are you saying [] isn't a
method? I guess the point is that if you define new methods on a type
derived from map/slice you can't collide/override their built-in
functionality.
...
>> If the language wants to hide whether something is on the stack or on
>> the heap, then why do we need "new" at all? (What use cases of 'new'
>> aren't covered by &MyType{}.
>
> ints, floats, complexes, strings.
You could certainly have:
x := &1
or
y := 1
x := &1
or
x := &(1)
Strings... I really don't know what "new(string)" gives you that is
actually something interesting. If strings are immutable, then it is
just a pointer to the empty string, right?
>
>> 4) new/make gets more confusing after type declarations. It is pretty
>> obvious if you see:
>>
>> x := make(map[string]string)
>>
>> x := new(map[string]string) # is "obviously" wrong
>
> Not in the least. It allocates a pointer to a zero-valued map.
> That's a perfectly sensible thing to do.
What can you do with a "zero-valued map"? I'm genuinely curious, because
it seems that to do any actual functionality with it, you have to point
it somewhere.
>
>> It is no longer obvious after a type declaration, especially at a distance:
>> type Dict map[string]string
>>
>> x := Dict{}
>> x := make(Dict)
>> x := new(Dict)
>
> I don't see the problem. All of those things make sense.
> Knowing which one does what you want is a matter of what
> you're trying to do.
So far, I don't yet understand when new(map...) is useful. If it *can*
be useful, but is rarely so, it seems like something that falls under
the "we should consider pruning this for clarity".
>
> Rather than trying to reconstruct this corner of the language,
> I wonder if we can find a form of words for Effective Go and
> the tutorial which is more effective at avoiding people
> garden-pathing down misunderstandings. I'm probably
> to close to the problem to see why it's a problem ...
>
> Chris
>
As a semi-outsider, the new/make distinction just sounds like something
"you just have to learn". And when it gets discussed, people seem to
justify it based on grounds of "that is how it works". Which is
something you can live with, but always feels like a bump. The fact that
it comes up often also hints at that. There may be no better way to do it...
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iEYEARECAAYFAk4VoQsACgkQJdeBCYSNAANMTgCePhOZQq8WgUWrlxHq7y22TVZu
MI8AniuU5syKOVwIdBEFZYsmo8N2/Y8Y
=gqxZ
-----END PGP SIGNATURE-----
Oh. Well, working on under-defined types that are renames of
types range already works on isn't what I was thinking of; I
was thinking of /any// user-defined type with "the right methods".
The Go team seem to be set against magic built-in methods,
and I can sort of see why, but it is a bit of a tripwire IMAO.
I'm pretty sure that:
x := new(map[string]string]
x["foo"] = "bar"Would have different effects if an implementation used a value rather
than a reference. (In reference version, it fails because you haven't
allocated the actual underlying object.)
>
> Please put me on the "wrong" list. There is nothing said in the Go specs how
> maps, channels, ... are to be implemented. One specific implementation of Go
> can have this easily as a pointer, another as a struct (i.e. a value) and it
> doesn't matter at all. The semantics of those two differing implementation
> are identical.
> Therefore, IMO you're wrong.
I don't think you can have the sharing properties of maps and slices
without a pointer or equivalent [1] being involved. This
isn't a property of the implementation, its a requirement placed
on the implementation by the spec.
a := make(map[string]string)
b := a
b["hello"] = "world"
... a["hello"] is now "world".
Chris
[1] eg an index into an implied array
--
Chris "allusive" Dollin
>> I don't think you can have the sharing properties of maps and slices
>> without a pointer or equivalent [1] being involved. This
>> isn't a property of the implementation, its a requirement placed
>> on the implementation by the spec.
>>
>> a := make(map[string]string)
>> b := a
>> b["hello"] = "world"
>>
>> ... a["hello"] is now "world".
>>
>> Chris
>>
>> [1] eg an index into an implied array
>
> You're right that some pointers are involved in any reasonable/valid
> implementation of a Go map. You're wrong in the assumption that therefore a
> map must be a pointer per se.
I did not make that assumption.
> It is enough if a map is a value type, where
> one of it's bookkeeping fields is e.g. a pointer to a bucket list of the
> map.
Yes. "... without a pointer or equivalent [1] being involved".
Maps share; HENCE the implementation requires pointers.
Your "value type" has to wrap a pointer to the sharable bit.
(It's not clear to me that there could be something useful in
the non-pointer part of the value object. Maybe a cache?)
Some types are "reference types", meaning copying the type's value with
an assign results in a second reference to the same value, and
changing either is reflected in both. This usually means the
implementation contains a pointer to (most of) the type's value, which
is allocated elsewhere.
If a type is as such, you need to know about it- although not, as you
claim, about how or why it is as such internally.
Some types require initialisation to be usable. If a type is like that,
you need to know about it, and know how to set it up yourself or what
functions are available for the task.
Some types are cheap to copy by value. Some aren't. It's good to know
about that, too, if trying to write performant code.
In almost all languages, user-defined types have all of the above
concerns. There's partial ways to reduce the last, like removing copying
by value entirely, with corresponding downsides. The second supposedly
can be fixed by constructors, but this simply replaces it with needing
to know what constructors are available taking what parameters, and what
the instance they produce provides.
The built-ins are not any more "inconsistent", than types in general
are in almost every other language. Could they avoid sharing the
concerns of the user-defined types? Maybe, with magic. But it wouldn't
make programming in Go easier; the built-ins are the easiest ones to
know about.
Go's types are no more inconsistent than types in many other languages.
It just doesn't try to hide it from you. This isn't a problem with Go,
this is a problem with people giving it a shallow look, seeing
differences between types, and failing to realise that they were
everywhere else, too, and they just hadn't known they needed to be
aware of it.
I did not make that assumption.
> It is enough if a map is a value type, where
> one of it's bookkeeping fields is e.g. a pointer to a bucket list of the
> map.Yes. "... without a pointer or equivalent [1] being involved".
Maps share; HENCE the implementation requires pointers.
Your "value type" has to wrap a pointer to the sharable bit.(It's not clear to me that there could be something useful in
the non-pointer part of the value object. Maybe a cache?)
No problem. It's a big hot thread.
> But the main idea here is IMO that regardless of the implementation, the
> semantics are in the specs and any valid implementation will behave the same
> way. Stated differently, it would be wrong, if the different implementation
> could be somehow observed/detected by code in a trivial way (sans unsafe
> etc.).
Concur.
Well, but what if knowledge of the compiler's internal implementation
details of a Go map exposes the essence of "What a Go map is, and what
'new' is" with utmost clarity? A clarity that cannot be obtained by
merely reading the specification.
Where else does this hold? It doesn't seem to be generally true. It
seems that in this case, the decision was made based on the
implementation rather than the language abstractions, but I don't see
that as being prevalent elsewhere.
I think Rob was talking about the Go memory layout control and I think he is correct. In Go basic/primitive types (int, arrays, ...) are directly mapped to their memory image, same for struct types (with padding applied) - no hidden VMT pointers added and no other/similar tricks.
Not applicable to non-primitive types (e.g. slices, interfaces, ...), where the memory layout is intentionally hidden from the programmer.
There probably may be something in the Go specs based/inspired by it's implementation, but I'm not aware of anything like that.
On Fri, Jul 8, 2011 at 4:37 AM, bflm <befeleme...@gmail.com> wrote:I think Rob was talking about the Go memory layout control and I think he is correct. In Go basic/primitive types (int, arrays, ...) are directly mapped to their memory image, same for struct types (with padding applied) - no hidden VMT pointers added and no other/similar tricks.Fair enough. I don't see how this is the same meaning as "reflecting the nature of the underlying machine model" as could possibly be used with reference to `new` though. `new` seems to better reflect the underlying C model, rather than the machine model.
Not applicable to non-primitive types (e.g. slices, interfaces, ...), where the memory layout is intentionally hidden from the programmer.And here we get to the exceptions.
The thing is, slices, maps and channels are exactly the types we're talking about here. We're hiding their internals, and yet the argument for them using `make` while pointers use `new` is based on those very same internals.
There probably may be something in the Go specs based/inspired by it's implementation, but I'm not aware of anything like that.And that's why `new` is such an oddity.
`new` allocates zeroed memory and returns a reference to it. `make` (for slices) allocates zeroed memory and returns a reference to it. The only way to distinguish the two is that `new` works like C allocation (take info about the element type and return a pointer to the memory allocated for it), while `make` works like a C factory function (plus generics), which is necessary because the implementation of slices requires some extra initialization.
At the end of the day, it seems kind of like painting the bikeshed a different colour. Whether you type `new(T)` or `make(*T)`, you get the same result (well, `make(*T)` would work better for named pointer types, but those aren't common).
But because of the slight difference in how they operate (`make` returns a `T`, `new` returns a `*T`) newcomers get confused, especially if they haven't been exposed to pointers much before.
So it's more like having a shed where one of the doors opens in and the other opens out, and there's no apparent reason for it except that that's how the contractor originally walked through the shed. If you use the shed everyday, you come to accept that this is how the doors work. But someone new will have trouble figuring out when they're supposed to pull and when they're supposed to push. If you make them both open the same way, the newcomer only has to care about which way they want to go, rather than worrying about how the door opens.
On Friday, July 8, 2011 7:46:54 PM UTC+2, Steven Blenkinsop wrote:On Fri, Jul 8, 2011 at 4:37 AM, bflm <befeleme...@gmail.com> wrote:I think Rob was talking about the Go memory layout control and I think he is correct. In Go basic/primitive types (int, arrays, ...) are directly mapped to their memory image, same for struct types (with padding applied) - no hidden VMT pointers added and no other/similar tricks.Fair enough. I don't see how this is the same meaning as "reflecting the nature of the underlying machine model" as could possibly be used with reference to `new` though. `new` seems to better reflect the underlying C model, rather than the machine model.In the case of simple/scalar and struct types I see the CPU memory model reflected 1:1 with the content of such entities. In C it's the same, i.e. both C and Go are "close to metal" in this.
Not applicable to non-primitive types (e.g. slices, interfaces, ...), where the memory layout is intentionally hidden from the programmer.And here we get to the exceptions.Intentional hiding of data structure (of non simplistic types) is a (IMHO good) design choice and not an exception. Even compared to other languages it's not an exception. Consider e.g. very popular (though not for me) Javsacript, which AFAIK has no control on memory layout at all - everything is opaque, every implementation can choose how to implement Javascript all entities.
Well, I can say only that I see nothing odd about 'new'.
`new` allocates zeroed memory and returns a reference to it. `make` (for slices) allocates zeroed memory and returns a reference to it. The only way to distinguish the two is that `new` works like C allocation (take info about the element type and return a pointer to the memory allocated for it), while `make` works like a C factory function (plus generics), which is necessary because the implementation of slices requires some extra initialization.There is a huge difference between new and make which some tend to overlook. 'v = make(T)' initializes an already existing entity 'v', but 'v = new(T)'' guarantees to (if succeeds at all) to return a pointer to - not surprisingly - a *new*, unique zero value of T. Analogically for ':=', but let me ignore some details for now.
The just mentioned difference between make and new make them so distinct for me, that I fail to understand, how those two builtins can be confused at all. They heave IMO mostly nothing in common, except for the detail of the magic understanding of a type name as a parameter.
At the end of the day, it seems kind of like painting the bikeshed a different colour. Whether you type `new(T)` or `make(*T)`, you get the same result (well, `make(*T)` would work better for named pointer types, but those aren't common).It's true, and it was discussed several times before, that 'make(*T)' can be considered as an (syntax sugar) replacement for 'new(T)'. What's left is the design choice made by the Go language designers (I'm not one of them). After diving myself into Go, I think they've made a good choice. Because aliasing the semantics of new(T) and make(*T) would/could - I guess - cause even more confusion for those, who already have trouble to get a grip on new vs make.
But because of the slight difference in how they operate (`make` returns a `T`, `new` returns a `*T`) newcomers get confused, especially if they haven't been exposed to pointers much before.Reiterated: IMO - the difference is big, what they have in common is almost irrelevant.
So it's more like having a shed where one of the doors opens in and the other opens out, and there's no apparent reason for it except that that's how the contractor originally walked through the shed. If you use the shed everyday, you come to accept that this is how the doors work. But someone new will have trouble figuring out when they're supposed to pull and when they're supposed to push. If you make them both open the same way, the newcomer only has to care about which way they want to go, rather than worrying about how the door opens.I do understand the words in this paragraph, I can parse the sentences, but I fail to get the message. My fault, granted.
There is a huge difference between new and make which some tend to overlook. 'v = make(T)' initializes an already existing entity 'v'
package mainimport ("fmt")func main() {v := make(chan int)fmt.Printf("%p\n", v)v = make(chan int)fmt.Printf("%p\n", v)}
0xf8400144600xf8400149b0
But the confusion would be,
Can I make(T)? What does that give me?
Can I make(*map[string]string)? What does that give me?
Why does make(map[string]string) return something initialized while
make(*T) gives me something that still requires initialization.
The same confusion is there, you just moved it a bit and the same
level of understanding is required to remove the confusion.
New users eventually have to understand the difference between the
functions of new() and make() even if we call them something else and
it's better that they get confused early on and learn than get too far
in with an incorrect understanding.
- jessta
--
=====================
http://jessta.id.au
v = make(T) initializes an already existing entity v based on an allocation it performs. v = new(T) initializes an already existing entity v based on an allocation it performs. Where T sits is different between the two cases (in the first, T is what is returned, in the second, T is what is allocated), but the difference has nothing to do with the types involved and everything to do with the chosen semantics of the functions. So yes, there is a distinction, but it's self generating, there's no external reason for it. You could also have newslice(T, 5) if you wanted to, but why would you?
On Sat, Jul 9, 2011 at 5:34 AM, Steven Blenkinsop <stev...@gmail.com> wrote:If make(*T) replaced new(T), there'd be no new(T) for people to get confusedabout.
But the confusion would be,
Can I make(T)?
What does that give me?
Why does make(map[string]string) return something initialized while
make(*T) gives me something that still requires initialization.
Why does make(map[string]string) return something initialized while
make([]map[string]string)/make(*[]map[string]string)/make([][]map[int]*[]map[string]string)...
The same confusion is there, you just moved it a bit and the same
level of understanding is required to remove the confusion.
New users eventually have to understand the difference between the
functions of new() and make() even if we call them something else and
it's better that they get confused early on and learn than get too far
in with an incorrect understanding.
I still can't agree with any (important) similarity. Maybe this code illustrates what I tried to say (about the differences) in a better way. Would there be an easy way to count the live/reachable instances of type Map and Int, then I believe the program will print 1 for N1 and 10 for N2.
The difference between the two uses isn't make and new, it's the choice to assign to a pointer vs its element.
I don't see how you can acknowledge that new(T) could be replaced by make(*T), and yet claim that there is no similarity between what make and new do.
On Friday, July 8, 2011 11:12:16 PM UTC+2, Steven Blenkinsop wrote:IMHO you're - I don't know why - confusing an entity and what an entity internally may point to. The later was nothing I was talking about. I'm sorry I failed to explain the problem in a better way.
IMHO you're - I don't know why - confusing an entity and what an entity internally may point to. The later was nothing I was talking about. I'm sorry I failed to explain the problem in a better way.Unfortunately not. I am quite clear on the difference between a pointer and its element. However, I also acknowledge that a pointer is a value in its own right, something you seem to be having trouble bringing yourself to acknowledge.
On Friday, July 8, 2011 11:50:26 PM UTC+2, Steven Blenkinsop wrote:IMHO you're - I don't know why - confusing an entity and what an entity internally may point to. The later was nothing I was talking about. I'm sorry I failed to explain the problem in a better way.Unfortunately not. I am quite clear on the difference between a pointer and its element. However, I also acknowledge that a pointer is a value in its own right, something you seem to be having trouble bringing yourself to acknowledge.Although I don't know what an element of a pointer is, I disagree again. It seems to me that I'm just repeatedly saying the very same as the language authors do about this topic, while that's not the case on the other side.
The element of a pointer is the thing returned by, given a var p *T,reflect.ValueOf(p).Elem().Interface().(T)Or, more simply:*pAnother way to say it would be "pointee", or "the value that p points to".
You aren't quite saying the same thing as what the language authors are saying. I'm sure the language authors realize that the choice of semantics is exactly that: a choice. You seem to be saying that the alternative semantics are illogical.
You then go on to attempt to demonstrate the current semantics in a way that just shows you don't actually understand them.
I'm sure the language designers understand their own language, most of the time (there have been a few instances where even they have been surprised).
I know what a pointee is, but I can't recall ever seeing it being called 'a pointer's element' before.
Google groups indentation levels make it clear for me, but for those who are using more traditional email clients, it may not be clear. My apologies. I was responding to r (is that Russ?). The CC for ⚛ was generated automatically by google groups, not sure why. I just left it as-is.
At risk of making this really long thread even longer, in C any pointer is functionally identical to an array, thus p[0] is the same as *p, and so whatever p points to is element 0 of p.
Yes. A more language-relevant source is in the reflect package, where the word element is used in this way.
IIRC, there used to also be similar usages in the spec, but they became unnecessary as things were streamlined.
Element is a nice general term for when you want to talk about similar things with similar language, as is the case in reflect.
It's interesting to point out that C's pointer and dynamic array aren't only similar, but the same thing. Go makes a distinction, which is safer, but that doesn't mean they're strangers.Here's an illustrative example of what I'm talking about:x := make([]map[int]*[]chan int, 1)x[0] = make(map[int]*[]chan int)x[0][0] = new([]chan int)*x[0][0] = make([]chan int, 1)(*x[0][0])[0] = make(chan int, 1)(*x[0][0])[0] <- 5fmt.Println(<-(*x[0][0])[0])Obviously, there is way too much composition going on here, but I think it helps the illustration. I'm not doing anything all that different on line 3 than on lines 1, 2, 4 and 5. The composite reference type I'm initializing just happens to be a pointer, so I have to use a different function with different semantics to initialize it. I didn't have trouble writing this code, but I sympathize with anybody trying to figure out why line 3 needs to break the pattern.
My favorite approach is to tell people about the most general
operational semantics of "new" and "make". Basically, this means to
tell them how "new" and "make" are implemented. The main point is that
the implementations are different. A consequence is that their results
have different types (T vs *T).
CS 101? ;-)
Dne 9.7.2011 16:30 "⚛" <0xe2.0x...@gmail.com> napsal/a:
Ok. I wasn't sure what you meant.
I wasn't sure because a good counter-argument to the "simply tell
people how it's implemented" approach is that people might then start
to assume that it is the only possible implementation, and base their
programming decisions on this assumption. It might cause problems if
the operational semantics they have been told about is correct, but
totally sub-optimal.
A related example: Sorting has multiple implementations: bubble sort,
quick sort, merge sort, bitonic sort, etc. The most simple
implementation is bubble sort. Unfortunately, bubble sort has the
worst performance. If a person only knows about bubble sort, it is
effectively preventing the possibility to use sorting to solve medium-
to-large problems.
On Jul 9, 3:59 pm, Martin Capitanio <m...@capitanio.org> wrote:
> On Saturday, July 9, 2011 3:25:42...