Mistakes C/C++ Devs make writing Go
about.sourcegraph.com
about.sourcegraph.com
(Please, don't start a religious war on programming languages. We are all grown ups)
Both C++ and python are rather fully featured languages, while the design of Go seems to aim for more simplicity and quality of implementation which affords a great developer UX. In this sense, Go is a step back from the complexity of both C++ and python.
Go for me feels more like an updated and slightly higher-level C for application or webserver programming than any sort of alternative for C++.
Go is statically compiled into a statically linked executable. Go has structs as complex types. They are handled by value, unless you specifically use pointers. Structs can contain other structs as values, without requiring the indirection and the overhead of references. The same applies for arrays. You can have methods on any type, including structs, but you can also just have functions.
So it has a scanner like Java or C#, but you’d get laughed at iterating bytes not objects in Java.
I’d also note Golang’s API is built for enterprises so it’s familiar by design.
I’d enjoy to hear other perspectives.
Yes
Ultimately, I think the right choice comes down to identifying what exactly your dissatisfaction with C++ is.
Disclaimer: I'm a huge fan of Rust and not much of a fan of Go, but I do have experience writing each of them, and I tried to focus on the positives of each language as they might appeal to the parent commenter
I would also suggest trying Python itself (why not go all the way?). Personally I write quick hacks in Python and real code in C++, and find Go to be an interesting middle ground. Python will do more to stretch your idea of programming. Maybe someday Go will get numpy and tensorflow.
Also C++ did not stand still and even the compile times are slowly becoming a thing of the past, with incremental compiler and linking, and modules support.
Maybe if Go 2.0 gets done right, it gets another look from us.
Many computations contain other functions, but the four arithmetic operations are very familiar and look godawful as add(multiply(5, 2), 7) instead of 5*2+7. All of linear algebra is aritmetics, as a prime example
Since Go is easier to learn, it probably couldn't hurt to learn it, then turn to Rust if it's not powerful enough for your use case.
It isn't quite as complex as C++, but has more meat to it than C.
If you really know C++, then you know it's easy to write safe C++ code with the latest standards so I'm not sure what's your problem here. Go and Rust brings very little to the table for an experienced C++ developer IMHO. Stick to the latest C++ features and you'll write "slightly less low-level code", although it's debatable how level C++ is to begin with.
If you are building an OS, game engine, database server or doing embedded programming where the gc is a problem, or if you need a memory/type safe program (at the expense of a bit of productivity), then use Rust.
func doSomethingTwice() error {
// Issue occurs below
errc := make(chan error)
go func() {
defer fmt.Println("done wth a")
errc <- doSomething("a")
}()
go func() {
defer fmt.Println("done with b")
errc <- doSomething("b")
}()
err := <-errc
return err
}
> When one routine writes to the channel, the program exits and the other goroutine is lost, building up memory use as a resultsI'm not very familiar with go, but my guess would be that since the two goroutines send to an unbuffered channel that is read at most once, the "slower" of the two goroutines will sit there blocking attempting to send to a channel that will never be read. So this would "leak" a goroutine that would consume resources while the process was still running, but it doesn't have anything to do with the program exiting.
https://golang.org/ref/spec#Channel_types
https://golang.org/ref/spec#Send_statements
> How do we fix this? We simply increase the number of channels to 2
The fix is okay but the language is a bit hazy, there's still only one channel, but now it's a buffered channel with capacity to hold up to 2 messages, so the two goroutines don't need to block waiting for a receiver to be ready to synchronously receive the message they are sending.
I was very disappointed with this explanation as well. The documentation is less than direct on this point, and suggests goroutines "execute independently", so my only conclusion is that the author doesn't understand channels very well, and was perhaps led to not understand them well.
01 func doSomethingTwice() error {
02 // Issue occurs below
03 errc := make(chan error)
04
05 go func() {
06 defer fmt.Println("done wth a")
07 errc <- doSomething("a")
08 }()
09 go func() {
10 defer fmt.Println("done with b")
11 errc <- doSomething("b")
12 }()
13 err := <-errc
14 return err
15 }
If the programmer understands the control flow is something like: 03, 06, 07, 10, 13, 14 then they're not going to be confused, and they're certainly not going to "fix it" by increasing the buffer, they'll fix it by reading from the channel twice, and guarding against this by closing the channel. The real question is how to explain to the programmer what the semantics of channels is:A goroutine blocks on read or write of an unbuffered channel (what we're seeing here).
The garbage collector is for simulating an infinite memory machine only. It is not for "cleaning up" after you.
The issue comes up when go programmers learn about runtime.GOMAXPROCS much too early, so they learn goroutines as threads, and they guess that the thread will be cleaned up when "garbage collected" because that's how Python works (or some other "garbage collected" language they might be familiar with). They're further confused because golang values have a "finalizer" so they might just assume that the thread finalizer (somehow) kills the thread, or the channel finalizer (somehow) closes the channel.
Perhaps if they noticed that writing to a closed channel causes a run-time panic, they might think these semantics are unlikely.
The point about buffering is to be able to write twice without blocking. Yes, you get 03, 06, 07, 10, 11, 13, 14 but how important was that really? If you did it just because you don't want to "leak memory", then you still don't know what's going on.
You don't want to read twice because then you would block waiting for both messages.
When the channel size is two the two inner writing goroutines can succeed on their write to the channel even if nothing reads from it.
The channel has three references, the outer receiver, and the two inner senders. The receiver finishes as soon as its able to receive the first message.
So what happens is one of the messages is sent and received, the other is sent and just sits in the channel. Then the channel has no more references and is garbage collected, so there's no memory leak.
Also goroutines are scheduled onto threads. So these orphaned goroutines that can't complete eat up memory (their call stack + the channel which can't be completed) as well as put pressure on the scheduler. (though the scheduler is efficient and can handle millions of goroutines) That's why it makes sense to refer to this as a memory leak.
Or is the correct fix to use errgroup[1]?
func doSomethingTwice() error {
var g errgroup.Group
g.Go(func() error {
defer fmt.Println("done wth a")
return doSomething("a")
})
g.Go(func() error {
defer fmt.Println("done with b")
return doSomething("b")
})
return g.Wait()
}
[1] https://godoc.org/golang.org/x/sync/errgroupSometimes. Sometimes it is not. For a trivial example, a unix-style wait would be done by reading twice. A job queue would be another.
It is not, for the reasons specified in the article, which falsely states that the buffer length and the number of goroutines has some relationship (# channels < # goroutines).
The example in the article is a common issue that comes up in go programs, and the suggested fix is what is usually recommended.
The other way to solve it would be to read the first error and then up another goroutine to read the remaining error, but that seems more complicated to me.
This is not true, for the reasons I outlined.
> The example in the article is a common issue that comes up in go programs, and the suggested fix is what is usually recommended.
That may be, but it doesn't change the fact that go programmers that take this advice are creating a kind of cargo-cult around channels and goroutines that will hold them back.
> The other way to solve it would be to read the first error and then up another goroutine to read the remaining error, but that seems more complicated to me.
If all you ever do with channels and goroutines is simulate threads, you're missing out. Erlang programmers might more-easily appreciate this given that they have to go through an incredible contortion to get this feature that go provides for free.
If you don't care about the results of the goroutine (here you explicitly don't care about the return value of at least one of the routines), a WaitGroup is much better.
import "sync"
func doSomethingTwice() error {
// Issue occurs below
var wg sync.WaitGroup()
wg.Add(1)
go func() {
defer fmt.Println("done wth a")
wg.Done()
}()
wg.Add(1)
go func() {
defer fmt.Println("done with b")
wg.Done()
}()
wg.Wait()
return nil
}
The more advanced concept than this, which isn't in the stdlib is errgroup (https://godoc.org/golang.org/x/sync/errgroup#Group.Wait), where instead the accompayning `Wait()` function can also return an error.In general I consider a code smell if you aren't reading every value off the channel.
So just casually one would handle the case:
if ret.err != nil{
//Handle error.
}Also why the C++ community has started to focus on teaching C++ the right way.
Instead, move the defer outside of any loops, and have the defer check the state of the variable to decide action. For example, in the case of an open file, the loop should close the file and nil out the reference. In the defer, if the reference is not nil, it closes the file.
the article is pretty cool, especially when the author actually has a family name Check. :)
2. There are no developers of C/C++.
3. C and C++ are distinct languages.
4. Anybody advertising for a C/C++ developer is insufficiently aware of software requirements to provide meaningful employment.
5. C++ programmers and C programmers have different responses to unfamiliar languages.
6. Some C programmers would like to be thought of as skilled "C/C++" programmers. There are none. Thus, they are not.
7. Many C++ programmers are capable of altering C code. They, also, are not "C/C++" programmers. When altering C code, they are programming in C, not C++.
8. C++ programmers and C programmers make different kinds of mistakes.
9. Anyone titling an article about "C/C++ programmers" make is making a fundamental category error, and has self-identified as lacking insight.
10. Anyone titling an article about mistakes "C/C++ programmers make" refers to an empty set of programmers.
11. People new to Go make mistakes. (One such mistake might be using Go at all; that is not decided.)
12. There will be some intersection between mistakes made by any two groups of programmers.
13. The intersection between mistakes made by Java programmers and Javascript programmers does not define a "Java/Javascript" language, nor a set of "Java/Javascript programmers", despite any similarity of names or surface syntax between the two.
I program professionally in C++. Waltzing into a non-C++ C code base requires a strong mental shift. I work with embedded programmers new to our team who have a strong history of C programming, and for them, C++ is a very steep hill to climb to become proficient.
They are two languages with interoperability and semantic similarities that share a compiler infrastructure and a runtime and memory model.
But modern C++ programming differs in drastic ways from C such that I don't think you can call them a unified language and brush over it with 'C/C++ programmers'
https://en.wikipedia.org/wiki/C/C%2B%2B_Users_Journal
Yes the languages are different, I would like to have C nuked instead of having to use it from time to time, but I agree with using C/C++ instead of writing C and C++ all the time.
It is a matter of English style only.
Remember "The C/C++ User's Journal"? The magazine of reference for C and C++ developers?
Google, Apple, HP, IBM, Microsoft, ARM, clang, gcc and many others have C/C++ scattered all around in their documentation, do you want to mail their documentation departments?
I haven’t seen any unicorns yet but I do seem to have found a quantity of gold. But no rainbow.