I’m an experienced pythonista. Lately I’ve been trying out Go by translating some of my small programs into it.
So far I like it, but I found some rough edges; some about ergonomics, some about not protecting from accidental errors.
As a begginer, I might be missing the right way to do it, but here are my thoughts so far.
(Is this the correct forum to discuss proposals?)
defer
in loop should be a compiler error
This compiles, but leads to nasty bugs:
files, err := ioutil.ReadDir("./")
if err != nil {
log.Fatal(err)
}
for _, f := range files {
name := path.Join("./", f.Name())
handle, err := os.Open(name)
defer handle.Close()
if err != nil {
continue
}
// just for the sake of example
if true {
continue
}
Backwards-incompatible, but probably there are no situations where it’s legit to use.
It could be just a warning to ease migration.
error checking should always be enforced
This compiles:
handle.Seek(4, 0)
It should be:
_, _ = handle.Seek(4, 0)
Backwards-compatible, gofix-able.
error handling: try operator
Compare this current code:
func main() {
fileName := "a file"
_, err := read(fileName)
if err != nil {
log.Fatal(err)
}
}
func read(fileName string) (uint32, error) {
handle, err := os.Open(fileName)
defer handle.Close()
if err != nil {
return 0, err
}
if _, err := handle.Seek(-4, 2); err != nil {
return 0, err
}
bs := make([]byte, 4)
_, err = handle.Read(bs)
if err != nil {
return 0, err
}
offset := binary.BigEndian.Uint32(bs)
if _, err = handle.Seek(int64(offset), 0); err != nil {
return 0, err
}
if _, err = handle.Seek(4, 1); err != nil {
return 0, err
}
bs = make([]byte, 4)
_, err = handle.Read(bs)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint32(bs), nil
}
… to this proposal inspired by Rust:
func main() {
fileName := "a file"
_, err := read(fileName)
if err != nil {
log.Fatal(err)
}
}
func read(fileName string) (uint32, error) {
handle, _ := os.Open(fileName)?
defer handle.Close()
handle.Seek(-4, 2)?
offset := binary.BigEndian.Uint32(handle.Read(make([]byte, 4))?)
handle.Seek(int64(offset), 0)?
handle.Seek(4, 1)?
return binary.BigEndian.Uint32(handle.Read(make([]byte, 4))?), nil
}
The code is far more readable, but still backwards-compatible.
functions: named args and default values
Compare this current code:
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
os.Mkdir(path, 0777)
} else {
log.Fatal(err)
}
}
With this proposal:
if _, err := os.Mkdir(path, existOk=true); err != nil {
log.Fatal(err)
}
Or better, with also my previous proposal:
os.Mkdir(path, existOk=true)?
Not backwards-compatible (?).