And then there is the business of being able to make a type automagically satisfy an interface. (Java may have fixed some of the agony of creating classes just to satisfy a required interface. Last I used Java, it didn't have delegates and whatnot.)
And then there is the business of being able to make a type automagically satisfy an interface. (Java may have fixed some of the agony of creating classes just to satisfy a required interface. Last I used Java, it didn't have delegates and whatnot.)
with open('foo') as f:
...
And the file will be closed after that block of code finishes executing. Similarly in Rust if you do "let file = File::open("foo")?;", when the variable drops the file will be closed.If you want to loop over 10k files, both of those handle that just fine, because the active file will be closed at the end of each loop. Go's defer will try to wait until after the loop to close everything. You can make things even more verbose to fix that, but it gets ugly real quick.
defer is more flexibly and explicit though; I rather like that. It's pretty unclear what exactly that "with" does, what you can and can't use inside "with", you can't "just" run any arbitrary code without creating your own class, and you sometimes end up with 3 or more levels of nested "with"s that could have been one defer.
In short, I don't think there's a clear winner. Both are clunky in some scenarios where the other is easier.
In my experience, if you're not checking errors, then you're often just going to crash loudly. Likely at a similar point that the comparable Python would have had its runtime error. In my experience, if Go would have continued silently, the comparable Python might also just continue silently anyway.
EVERY memory allocation can fail. And I mean EVERY.
var x := 5 // where's the error handling?
EVERY kernel call can fail. Even this is still not a 100% correct way to call fmt.Printf("Hello, World!"): s := "Hello, World!")
writtenSoFar := 0
while writtenSoFar < len(s) {
bytesWritten, err := fmt.Print(s[writtenSoFar:]) // and even this is a recent syntax addition.
if errno, ok := err.(syscall.Errno); ret == -1 && ok {
// error signaled
if errno == C.EAGAIN {
time.Sleep()
continue
} else {
return err
}
writtenSoFar += bytesWritten
}
If you don't do this, you will find, for example, that writing large amounts of data to a network socket suddenly only sends half the output to the other side. Plus anything could set O_NONBLOCK on stdout, which would require this. And time.Sleep() is required in some cases where the program redirects os.Stdout to itself.Even this does not take have proper reactions if an OOM occurs somewhere. So it is still not correct.
It's like C. Simple Go looks correct and just chugs along, destroying data instead of crashing. This makes people feel programs run correctly ... but they don't.
This issue, not checking the number of bytes written, usually combined with incorrect EAGAIN handling, is a pretty pervasive problem in network programming. You will find the closer you get to 100% cpu usage, the more common this problem becomes, just like threading bugs. It's one of the ways a service goes from handling 5 Gbit at 90% cpu usage, then handling 5 kbps at 95% cpu usage (because everything suddenly errors out, then retries eat all the bandwidth). It's impossible to find if you don't know what you're looking for.
It's not this issue specifically: Golang programs, like C programs, are strongly incentivized to just keep going with incorrect data when other languages would crash.