Hello Go enthusiasts,
As part of familiarizing myself with Go, I stumbled over lots of questions, criticism and whatnot. While writing all that down and backing it up with some research, it transformed into a thorough review and introduction into Go, aimed at seasoned programmers that are new to Go. You might find this interesting. I certainly learned a lot about Go while writing it. I'd appreciate corrections and feedback, especially on the "open questions" section at the end. I will update the document with all insights I gain here.
http://www.syntax-k.de/projekte/go-review
I'm now porting one of the apps I maintain to Go. Keep up the good work!
--
Cheers,
Jörg
On Middeweken, de 8. Juni 2011, Kyle Consalus wrote:
> Your review seems to say that in Go you must (or should) have one file
> per package.
> This is not the case. Multi-file packages are common.
> It is true that multi-package files aren't allowed.
Oh, thanks for the correction. I now discovered the FAQ entry. I will correct that.
--
Cheers,
Jörg
The Parens aren't optional, per se. They're unnecessary. You can
include them but only where you could use them with an expression,
that is:
for(i := 0; i < 10; i++) {}
is a syntax error.
In the implict declarations section it's probably overly confusing to
have the type of the return value be the name of the function
returning it, something like "var foo Baz = Bar(1, 2, 3)" would keep
it reading like a constructor for people unfamiliar with Go's lack of
them.
For variable declaration being backwards, it's probably better to show
the difference between "var a, b *int" in Go and "int* a, b;" in C.
range isn't a function. It's purely a syntactic artifact used to
signal that it's a 'for each' style loop.
There's no dynamic loading, but I believe that's on the roadmap.
> How high is the memory overhead of interfaces?
http://research.swtch.com/2009/12/go-data-structures-interfaces.html
As far as structs and arrays they take up the space you tell them to
and no more.
You can only define methods on types you create in the current
package. It makes the compiler's job way easier, but more importantly
it makes the reader of your code's job easier—which is one of my
favorite things about Go :)
You cannot add methods to interfaces.
Go is less than 2 years old (well since it's initial public release,
anyway). It's a baby. Precocious lad, though.
Excellent write up. Thank you for taking the time. I'm sure others
will find it very useful.
"At best, there would be a community proposal process (like Python,
Java, Perl6, XMPP, etc. have) which tracks these suggestions, outlines
requirements that must be fulfilled for a given proposal to be
seriously considered, and summarizes the current state of development.
Then, ideas can mature, can be rejected with comprehensible arguments,
can be replaced by even better ideas, and finally make it into the
language without compromising its design goals."
This is something I'd love to see as well.
Nice doc. Thanks for taking the time to write it.
> like range().
range is a keyword, not a function. Parens after it are unnecessary
in prose and almost always in code too.
> Unfortunately, Duck Typing is incomplete. Imagine an interface like this: ...
That doesn't work because the memory layout of a MyObj
is different from the memory layout of an Arithmetic.
Other languages struggle mightily with this even when
the memory layouts match. Go just says no.
> I am not sure if I am happy with that pointer/value business.
> The all-things-are-references semantics of Java are much simpler.
That's true, but in return for having values you get more fine-grained
control over memory layout. See the beginning of this post,
before the first section heading:
http://loadcode.blogspot.com/2009/12/go-vs-java.html
> Does inlining work? How could cross-package inlining work?
It doesn't work yet but it is on the short list of compiler work.
Cross-package inlining can work because the import reads
compiler output, so the compiler can leave information for
itself to use when importing the package.
> Would it be possible to make Go 8-bit capable?
> C scales down to 8-bit microcontrollers. What is the minimum
> hardware a (possibly limited) Go runtime can work?
Go requires that int be at least 32 bits and that int64 and uint64 be
supplied; practically that means we've given up on 8- and 16-bit CPUs.
> Can Go object files be loaded at runtime?
> Do types implemented in these files integrate
> seamlessly into the hosting application?
They can't. There's no fundamental reason, just that we
started with fully-static linking and that simplifies a variety
of things.
> What are the limitations of the optimizer?
> Assuming gccgo, does it leverage a similar level of
> optimization as when compiling C++, or are significant
> steps unsuported?
For 6g and 8g, it does basic registerization, some
strength reduction, and trivial bounds check elimination.
Basic inlining and escape analysis (both cross-package)
have concrete plans and are likely before too long.
The most glaring things missing are some better constant
and condition propagation in order to remove more bounds
checks. None of this is fundamental, it's just not there yet.
It's important to remember that unlike most of the languages
it gets compared to, there are tons of open opportunities for
making Go run faster and smaller.
> Would it be possible to detect integer overflows, another
> well-known security defect? With operator overloading,
> I guess it would be easy to implement a guarded data type.
It's not possible in the language as it stands today, because
Go (unlike C) defines integer operations to wrap around.
There is no undefined behavior on overflow.
> How high is the memory overhead of interfaces? Of structs? Of arrays?
http://research.swtch.com/2009/11/go-data-structures.html
http://research.swtch.com/2009/12/go-data-structures-interfaces.html
http://blog.golang.org/2011/01/go-slices-usage-and-internals.html
> How high is the CPU time overhead of an interface method dispatch?
It's a vtable call, just like a C++ virtual function call.
> Of creating a goroutine?
It's a memory allocation.
> Of scheduling goroutines?
It's a few lock operations. The scheduler is a trivial round-robin
so it can make some dumb decisions, but it will improve.
> Is it possible to add methods to types not declared in the
> current package? Maybe even to int64?
No. No, unless you define your own type, as you did
with Handler in the write-up.
> Is it possible to add Methods to interfaces, i.e. a method that has
> the interface as receiver and thus is the same for all objects
> implementing that interface?
No, but you can write a function that takes one of those
interface values as a parameter. Allowing methods on interfaces
would complicate the decisions about what methods a particular
value has, especially since most values satisfy an interface
implicitly.
Russ
Could you unpack that a little?
Chris
--
Chris "allusive" Dollin
I think you have it backward. The part about types larger
than a machine word only applies to interface values.
They are the special case.
Russ
func test (param1 int) {
return
}
And related to this. I found a couple of situations where a declared
variables are not needed. For example while bechmarking a function call
(and the result of the function is not used anymore) or debugging. What
is the best way of avoiding this error?
Currently I am using something like:
res := foo()
if res == nil {}
or a similar comparison depending on the type of "res".
M;
Although it is a bit annoying sometimes, I like that go complains about
variables (and packages) declared and not used. It "forces" me the habit
to write cleaner code.
But why this doesn't happen in function parameters?func test (param1 int) {
return
}
And related to this. I found a couple of situations where a declared
variables are not needed. For example while bechmarking a function call
(and the result of the function is not used anymore) or debugging. What
is the best way of avoiding this error?
Currently I am using something like:
Not that annoying. Just use _ as the names of params you don't care about.
--
matt kane's brain
http://hydrogenproject.com
capoeira in boston http://capoeirageraisboston.com
aim -> mkbatwerk ; skype/y! -> mkb218 ; gtalk -> mkb.dirtyorg
i think part of the reason is that function parameters can be
visible in externally visible documentation - it's useful to be
able to name them even if they're not used.
Hmmm... and why do you want to document parameters that are not used in
the function? For me, such documentation is buggy.
M;
Yes, you are right. This is valid:
func test () int {
return 1
}
func main () {
_ = test()
}
And this answers my second question.
Thx,
M;
Hmmm... and why do you want to document parameters that are not used in
the function? For me, such documentation is buggy.
I've translated this review into Chinese with out your permit. Hope you don't mind that.
Ok, the former could be a common case.
Thanks,
M;
> Although it is a bit annoying sometimes, I like that go complains
> about variables (and packages) declared and not used. It "forces" me
> the habit to write cleaner code.
> But why this doesn't happen in function parameters?
>
> func test (param1 int) {
> return
> }
An unused variable is probably a bug. An unused package is a dependency
you can clean up. An unused parameter doesn't really indicate a bug or
a dependency issue. If the function exists to implement an interface,
then an unused parameter is entirely normal.
> And related to this. I found a couple of situations where a declared
> variables are not needed. For example while bechmarking a function
> call (and the result of the function is not used anymore) or
> debugging. What is the best way of avoiding this error?
> Currently I am using something like:
>
> res := foo()
> if res == nil {}
>
> or a similar comparison depending on the type of "res".
For this example I would just write
foo()
More generally, use
_ = res
Ian
I don't understand.
You can copy struct values happily so long as they either
have no unexported fields or you're in the struct-defining package.
> And pass by value is a reference to a copy?
Pass by value is pass by value, ie, a copy of whatever it is
you're passing, be it a struct or not. Of course, if the value
is (or contains) a pointer, the pointer (but not the pointee) is
copied, which is how maps & slices have the effect they do:
they're fat pointers.
If you pass a non-interface value to an interface variable,
then that value gets copied.
(The only way to avoid copies is via
> pointer syntax? interface syntax?)
The way to pass things by reference is to pass pointers
to them. This avoids copying the thing at the cost of
copying a pointer instead.
There's a tricky bit of implicit referencing that goes on
when x.f(y) calls a method f defined on a pointer type but
x is a variable of the pointed-to type: its address is
implicitly taken.
> I must have misunderstood something in my off hours reading.
> This is the primary cause of my thinking that reference syntax and semantics
> have been truncated in an awkward way.
I still don't understand what this means. Example?
There is no implicit copy on assignment. All assignments create
copies. Everything in Go is a value. There are no reference semantics.
Some values are reference values by virtue of referring to data which
is shared by all copies of that value. A pointer is a value that
refers to a single other value of a specific type. If you pass around
or assign a struct{X, Y int}, then this value is copied each time. If
you pass around or assign a *struct{X, Y int}, then the pointer
(reference) is copied, but whatever it refers to is not. Slices, maps,
and channels are also reference values with different properties from
pointers.
An interface variable can store values of any type that satisfies the
interface. Semantically, the thing inside the interface is copied and
passed around just like it would be if it weren't in the interface.
However, interfaces are implemented as having a fixed size. If the
value the interface variable is storing is too large, Go's
implementation has to allocate space for the value separately and put
a pointer to it in the interface variable. Since there is no way to
mutate the value in the interface, Go doesn't have to copy this
separate value each time you copy the interface, it just has to copy
the pointer. This, however, is just an optimization. If there were
some way to mutate the value in the interface, Go would have to copy
it each time you copied the interface. When you put a value into or
take it out of an interface variable, it makes a copy.
Your comments would reinforce my concern. "All assignments create copies" means "assignment copies implicitly" for values without a natural implicit copy (machine words).In assembler, for example, a move instruction is a copy. The machine has no other semantics to offer from its electrical foundation.In software, it's conventional to employ a machine word as an address to a location with a data structure. The ability to control the use of that data structure is a principal concern in Go.
> There's a value of struct syntax with implicit copy on assignment? And pass
> by value is a reference to a copy? (The only way to avoid copies is via
> pointer syntax? interface syntax?)
>
> I must have misunderstood something in my off hours reading.
>
> This is the primary cause of my thinking that reference syntax and semantics
> have been truncated in an awkward way.
I think it's a lot easier to consider that everything in Go is always
passed by value, that channels, maps, and functions are pointers, and
that slices contain pointers. Of course, for that it helps that I come
from a C/C++ background.
Ian
Intype Foo struct {a int64b int64}var Bar = new (Foo)var GeeWhiz = BarWhat I understand is that GeeWhiz is a copy of Bar, such that Bar != GeeWhiz
Meanwhile, back at the ranch, Bar.a == GeeWhiz.a, using a nice reference syntax.
However the reference syntax is broken at "GeeWhiz = Bar" by the copy in the assignment.
][ the reference syntax is broken at "GeeWhiz = Bar" by the copy in the assignment.
(An explicit construction of a pointer)
> var GeeWhiz = Bar
(The [pointer] value of GeeWhiz is the same as that of Bar)
> What I understand is that GeeWhiz is a copy of Bar, such that Bar != GeeWhiz
What makes you think this? It is incorrect.
> Meanwhile, back at the ranch, Bar.a == GeeWhiz.a, using a nice reference
> syntax.
The values of those expressions are the same because
they are the values of the same location.
> However the reference syntax is broken at "GeeWhiz = Bar" by the copy in the
> assignment.
How can a statement in the language break its syntax?
On Wednesday, June 8, 2011 8:37:00 PM UTC+2, John Pritchard wrote:
What I understand is that GeeWhiz is a copy of Bar, such that Bar != GeeWhizNo, GeeWhiz has type *Foo by inference.
However the reference syntax is broken at "GeeWhiz = Bar" by the copy in the assignment.
The logic here is shared by a number of languages,
including C, which also has what you choose (for some
reason) to call "implicit copies" of struct values.
> Both of which may be seen as risks to the
> longevity of this interesting new lang.
Maybe.
Yes. Bar is a pointer of type *Foo. GeeWhiz is a pointer whose value
is a copy of Bar's value. However, they both point to the same Foo
value, which does not get copied in the assignment.
> such that Bar != GeeWhiz
No, Bar == GeeWhiz. They are separate variables (one being a copy of
the other) so &Bar != &GeeWhiz.
> Meanwhile, back at the ranch, Bar.a == GeeWhiz.a, using a nice reference syntax.
No reference syntax. Bar.a is short for (*Bar).a. Since Bar ==
GeeWhiz, *Bar and *GeeWhiz are at the same location in memory, so
Bar.a == GeeWhiz.a by definition.
> However the reference syntax is broken at "GeeWhiz = Bar" by the copy in the assignment.
No reference syntax exists to be broken. The assignment only copies
the address. It doesn't copy the Foo that Bar points to.
Implications include those of what I'd call implicit copies, and a confusing logic of syntax and semantics. Both of which may be seen as risks to the longevity of this interesting new lang.
I strongly suggest you go here: http://golang.org/doc/play/ to
investigate and test out your assumptions about how pointers work.
I would call a memcopy under the covers an implicity copy if it's a side effect of something like an assignment
An assignment of a value of type 'struct' also
means the same thing in Go that it does in C: the
struct is copied.
In fact, unlike C, an assignment in Go *always*
means that there is a copy being made. That's
what assignment does: it copies the value on the
right hand side into the storage used for the value
on the left.
It doesn't matter whether you are copying a pointer,
a struct, an array, a function, or whatever: the data
representing that value gets copied.
It's much simpler than it is being made out to be.
Russ
What language are you coming from where an assignment *doesn't* create
a copy? Copying *is* the effect of an assignment, not a side effect.
Even in, say, Java, an assignment creates a copy. Objects in Java are
references, so copying them does not copy their contents.
Why?
The whole /point/ of an assignment is to do a copy: the
target variable's value becomes a copy of the source
expression's value.
Whether it's done by a call to a memcpy-like function,
a single machine instruction like MOV R1, #17, or a
bunch of machine instructions like TAD x; DCA y or
LDM Rs, {R4, R5, R7}; STM Rd, {R4, R5, R7} is
only an (implementationally important) detail.
> Of course all of this points to some needs in the docs
To explain what, exactly?
Chrus
--
Chris "allusive" Dollin
Now that's what I call a community. You're away for a single day and heaps of feedback. Thank you all!
I have updated the review, it should address all questions and incorporate all answers I have gotten.
Find it at the old place:
http://www.syntax-k.de/projekte/go-review
Two new questions have popped up, maybe someone can enlighten me?
About the garbage collector: How well does it handle large numbers of small objects?
About scheduling goroutines: Can a goroutine starve all other goroutines and the main program if it runs into an endless loop, or is there some sort of preemption as a safeguard?
--
Cheers,
Jörg
About scheduling goroutines: Can a goroutine starve all other goroutines and the main program if it runs into an endless loop, or is there some sort of preemption as a safeguard?
there is no guarantee. currently with GOMAXPROCS=1
(the default) there is no preemption, and a looping goroutine can starve
all other goroutines.
I feared this would be the case. Is there something fundamentally different with GOMAXPROCS > 1 or do two looping goroutines starve a GOMAXPROCS=2 program?
--
Cheers,
Jörg
$ cat t.go
package main
import "fmt"
func main() {
c := make(chan bool, 0)
go func() {
for {
}
c <- true
}()
for {
select {
case <-c:
fmt.Println("shouldn't happen")
default:
fmt.Println("bumblebee")
}
}
}
$ 6g t.go; 6l t.6
$ export GOMAXPROCS=1
$ ./6.out
bumblebee
^C
$ export GOMAXPROCS=2
$ ./6.out | head
bumblebee
bumblebee
bumblebee
bumblebee
bumblebee
bumblebee
bumblebee
bumblebee
bumblebee
bumblebee
^C
This is a problem with the current Go implementations.
It is not a problem with Go the language.
Russ
aha!
> About the garbage collector: How well does it handle large numbers of
> small objects?
There is a small amount of per-object overhead in the garbage collector.
There is no particular other problem with large numbers of objects, big
or small. If you want to slow down the current garbage collector, the
best way is to have a bunch of medium sized arrays and only keep
pointers to elements toward the end, with no pointers to the start of
the arrays. That won't give you a major slowdown, but it will increase
the constant factors of dealing with each object, which I would expect
to be measurable although I haven't tried.
> About scheduling goroutines: Can a goroutine starve all other goroutines
> and the main program if it runs into an endless loop, or is there some
> sort of preemption as a safeguard?
As others have said, there is no preemption at present when using 6g/8g.
These are good points to note at present but of course we are continuing
to work on the garbage collector and the scheduler. They are not part
of the language definition.
Ian
In particular, while the gccgo implementation of goroutines has many
deficiencies, it does not suffer from this particular problem.
Ian
In your review, you wrote that "A decent garbage collector canactually be faster than manual memory management by postponing
bookkeeping until there is time and completely avoiding bookkeeping by
reusing unused objects". In my opinion, this isn't true. Moreover, the
term "postpone bookkeeping until there is time" is non-sense (well, at
least to me). There is no way for *any* garbage collector to be
faster than *highly-optimized* C code compiled with an optimizing
compiler (GCC, ICC, CLang or whatever compiler) - it may sound
unbelievable, but it is the truth.
On Dunnersdag, de 9. Juni 2011, Ian Lance Taylor wrote:
> These are good points to note at present but of course we are
> continuing to work on the garbage collector and the scheduler. They
> are not part of the language definition.
I am totally aware of the fact that other things are more important. I just wanted to know (and document ^^) them. I agree with your implication, the language definition has highest priority, it must be right. Everything else is implementations and library.
You may be interested in the discussion my article has evoked in several places, like:
http://news.ycombinator.com/item?id=2631964
http://www.reddit.com/r/programming/comments/hudvd/the_go_programming_language_or_why_all_clike/
http://lwn.net/Articles/446598/
I've updated the feedback section to also address them. It's good to see some more controversial discussion, it gives you an outside perspective on things. In here there's more of a fan group :)
On Dunnersdag, de 9. Juni 2011, ⚛ <0xe2.0x...@gmail.com> wrote:
> In my opinion, this isn't true. Moreover, the
> term "postpone bookkeeping until there is time" is non-sense (well, at
> least to me). There is no way for *any* garbage collector to be
> faster than *highly-optimized* C code compiled with an optimizing
> compiler (GCC, ICC, CLang or whatever compiler) - it may sound
> unbelievable, but it is the truth.
Postpone meaning, if you're in a tight, time-critical loop that constantly allocates and deallocates a few objects, it's way faster to reuse those deallocated-but-not-freed objects and have a full GC run later when that loop is over and CPU time is left (typical UI workload!). The free list approach is used by GCs as well as by reference counters to great success, but in traditional C you'd have to do manually what these do automatically.
There was a truly great paper by Dan Sugalski (of Perl6 fame) explaining the (back then, like many years ago) up and coming Perl6 GC in much detail, covering many different approaches to GC and how they evolved. A very inspiring read. I tried to find it for my article already, but alas, Google failed me. Should you find it, read it and send me a link.
Also, what Dmitriy said.
--
Cheers,
Jörg
I know what you mean. A problem I see here is that you might be
> The free list
> approach is used by GCs as well as by reference counters to great success,
> but in traditional C you'd have to do manually what these do
> automatically.
overestimating the capabilities of GC algorithms. They are just
algorithms, so they are *constrained in their capabilities*, they
don't have knowledge about the problem space. A skilled programmer
will in most cases beat any GC in existence (even if he/she has to
invent a new or improve an existing memory management strategy along
the way).
Ok, a small example might better reveal what I mean: Imagine you have
a GUI application written in Java. You are using the class "Point" in
the code, which has two fields "x" and "y" of type "int". Each
instance of the class "Point" has to be allocated via "new
Point(x,y)". Out of these allocations, only a small percentage (if any
at all) actually needs to be allocated on the heap or from a pool -
for the most part, instances of "Point" are so simple that it would be
sufficient to allocate them in registers or in the stack frame of the
function. The crucial question is: Is Java's GC capable of determining
which instances of "Point" do *not* need to be allocated from the heap
nor from any pool? Are *you* able to answer this question? Compare
to what a C/C++ programmer would do: "Point" is a structure normally
passed by value.
Therefore, the C/C++ programmer will beat Java's GC
algorithm without even thinking about it! It may sound unbelievable,
but it is the truth.
In addition, another crucial factor in Java
code is not the capabilities of the GC algorithm, but the fact that
the Java *programmer* does not know (and does not even care!) whether
Java's GC algorithm will be able to determine which instances of
"Point" can be allocated in registers of the CPU or in the stack frame
of the function.