I have my own complain against Rust's error handlings, but I can't bring myself to praise what Go has been doing.
One problem with the error interface is that you can't know exactly which error type will be returned, and the Go authors may add new error types form time to time. `net.OpError` itself is a great example for that, which is a newer type than net.Error.
This style of error signalling is best used when the error handling is binary, either "No error, continue" or "Errored out, abort", but not branched out path like "If encountered error A, do this. If encountered error B, do that. Otherwise, abort".
Rust's error handling encourages such branched out handlings by default, but if you want to so the same in Go, there will be a nightmarish manual type discovery and tracking operation waiting for you.
So for me, I rather use a dedicated signal code to tell which error branch I should do next rather than relying on the error, i.e:
if next, err := func(); err != nil { // If errored, abort it
return err
} else if next == condition1 {
return op1()
} else if next == condition2 {
return op2()
} else {
panic("unsupported condition")
}