Much easier than implementing the error interface in go.
Rust is powerful enough to allow macros to remove annoying boiler-plate, and so most people using rust will grab one of the error-handling crates that are de-facto standard and remove the minor pain you're talking about.
In go, it's not really possible to do this because the language doesn't provide such macros (i.e. the old third-party github.com/pkg/errors wanted you to implement 'Cause', but couldn't provide sugar like 'this-error' does for it because go is simply less powerful).
I've found implementing errors in go to be much more error-prone and painful than in rust, and that's not to mention every function returning untyped errors, meaning I have no clue what callers should check for and handle new errors I add.
Let's look at a common example: you want to return two different types of errors and have the caller distinguish between them. Let me show it to you in rust and go.
What's the difference between importing some hundred's of lines from thiserror in rust vs importing the "error" package in go?
If the difference is just "It's okay to use stdlib code, but not dependencies", then go's a great language by that metric.
I don't think that's what matters at all. What matters is the abstraction that the programmer deals with.
In go, the abstraction I deal with, as a programmer, is what I showed above.
In rust, the abstraction I deal with is also what I showed above. One of those things is simpler.
Further, the abstraction in go is leakier. My function returns a 'error' interface even if it can only be those two concrete types, so the caller of my function has to step into my function, reason through it to figure out all the error variants, and then check them with 'errors.Is' or such, and changes to what variants are returned aren't statically checked.
In rust, I can look at the type-signature, write a 'match' statement, and never have to manually figure out all the variants returned, since the compiler just knows.
My point here is that what matters is the abstraction that programmers interact with. Third party libraries are bad when they give a leaky abstraction that causes the programmer pain. Standard libraries likewise.
In this case, the abstraction available in rust is cleaner and requires less code for me, the user of the package, so I don't think the lines of code used to build that abstraction matter.
I addressed that with the start of my comment: "Let's look at a common example: you want to return two different types of errors and have the caller distinguish between them"
Yes, your example implements the error interface, but it's not realistic. There is real go code that does that, but that's the reason I have to do string-matching type error handling in go, and frankly it's an argument against the language that its error handling is such a mess that some people see that as reasonable.
Having code that does
return errorString("You have to do string matching on me internal error")
is something you can do, sure, but it's not good code. Idiomatically in go, it would also be "errors.New", not this errorString type you made.
In rust, that would also be just as easy since it would be:
bail!("oh no internal error")
using anyhow, which is the de-facto standard way to do crappy string-style erroring, like the above.
But that's not what I want to talk about since that sort of error handling isn't interesting in either language, and isn't where they differ.
> In this case, the abstraction available in rust is cleaner and requires less code for me, the user of the package, so I don't think the lines of code used to build that abstraction matter.
> Why do you see this as something that matters?
It's true that the abstraction in rust has more code underlying it, but why does that matter?
If you de-facto never have to implement the rust error trait by hand due to excellent library support, then it's a moot point, isn't it?
Anyway, my examples above did implement the error trait, simply by deriving it.
> Are you unable to just write Rust without importing something?
Rust does have less in the stdlib, yes. If you want a json serializer in go, that's the stdlib. In rust, it's the serde package.
Rust is intentionally a smaller standard library. I don't think there's anything wrong with that, and I personally prefer it since it allows for more innovation on things like http / json / error interfaces / etc.
I don't know why you're phrasing it like it's a bad thing.
Thats a good outlook. Dont worry about any problem, just import something an its fixed! More imports the better! Hopefully we can do away with all code, and just import everything.
Well, before Go 1.13 came out, every decent production codebase in Go had to import Dave Cheney's pkg/errors[1] unless you totally gave up the idea of having useful error traces.
Go 1.13 has incorporated most of what this package does into the standard library. It really doesn't matter much whether the code is in a de-facto standard package or folded into the standard library IMHO, but Rust could do the same.
Rust did partially do this; one of the previous generation of packages here, failure, had most of its ideas eventually rolled into std::errror::Error, which enabled this next generation of packages (anyhow, thiserror, miette, eyre... the only one I'm not sure about is snafu, which did exist before, but I'm not sure if it got more improvements due to these changes or not.)
> Dont worry about any problem, just import something an its fixed! More imports the better!
We're talking about error boilerplate here.
Just as I do not write assembly by hand, but instead let the compiler generate that, just as I don't write http clients by hand, but let someone else do that, yes I would also rather not write error boilerplate by hand.
I would love it if I didn't have to write as much code to solve problems, but I recognize that the problems I'm solving are mostly specific enough that they require bespoke code specialized to the problem space.
However, error handling? Yeah, I don't care to hand-write that.
Said less glibly, I will happily import stuff when it makes my life easier, and write it myself when that is better.
You're absolutely right about implementing the `Error` trait yourself in Rust, it's a pain! And with many things, the Rust team felt it was better to leave error experimentation to an external library, as the community can iterate more quickly and settle on a favorite. At the moment, that favorite is `thiserror` (for defining errors in a library) and `anyhow` (for working with errors in application code)
That's been a pretty common pattern in Rust - leave some less clear parts of the standard library out for external crates to implement and experiment on. So you'll see almost all big Rust projects use one of those error handling libraries. You _could_ implement it with just the standard library, but you're just giving yourself more work for little to no gain.
Here's an example without using external libraries:
Not particularly good or bad (and I didn't even replicate all the functionality that `thiserror` gives you).
So yeah, Go and Rust have different philosophies for their standard library, and you can argue about which is better, but I don't think one can be proved objectively better than the other.
The Rust code is doing much more than what the Go code is doing, it's making more incorrect cases impossible. I really don't understand the complaint about imports. Anything that's not built into the standard library is not valid somehow? I'm struggling to imagine what would make someone think this.
Yes. I do it frequently. "#[derive(Error, Debug)]": https://github.com/dtolnay/thiserror#example
Much easier than implementing the error interface in go.
Rust is powerful enough to allow macros to remove annoying boiler-plate, and so most people using rust will grab one of the error-handling crates that are de-facto standard and remove the minor pain you're talking about.
In go, it's not really possible to do this because the language doesn't provide such macros (i.e. the old third-party github.com/pkg/errors wanted you to implement 'Cause', but couldn't provide sugar like 'this-error' does for it because go is simply less powerful).
I've found implementing errors in go to be much more error-prone and painful than in rust, and that's not to mention every function returning untyped errors, meaning I have no clue what callers should check for and handle new errors I add.