Hacker News new | ask | show | jobs
by svnpenn 1543 days ago
> easily implement for your own types

have you ever actually done that? I have, its not easy. Please dont try to hand wave away negatives of the Rust type system.

2 comments

> have you ever actually done that? I have, its not easy.

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.

> Much easier than implementing the error interface in go.

is this a joke? You have to import a third party package, just to implement an error interface? Here is Go example, no imports:

    type errorString string

    func (e errorString) Error() string {
       return string(e)
    }
It was not a joke.

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.

Rust:

    #[derive(Error, Debug)]
    pub enum MyErrors {
       #[error("NotFound: {0}")
       NotFound(String),
       #[error("Internal error")]
       Internal(#[source] anyhow::Error),
    }
The equivalent go would be something like:

    type NotFoundErr struct {
        msg string
    }

    func (err NotFoundErr) Error() string {
        return "NotFound: " + err.msg
    }

    func (err NotFoundErr) Is(target error) bool {
        if target == nil {
            return false
        }
        // All NotFoundErrs are considered the same, regardless of msg
        _, ok := target.(NotFoundErr)
        return ok
    }

    type InternalErr struct {
        wrapped error
    }
    
    func (err InternalErr) Error() string {
        return fmt.Sprintf("Internal error: %s", err.wrapped)
    }

    func (err InternalErr) Unwrap() error {
        return err.wrapped
    }
I dont think you realize how ridiculous this comment is. Youre comparing 10 lines of Go, with 200 of Rust:

https://github.com/dtolnay/thiserror/blob/master/src/lib.rs

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.

Why do you see this as something that matters?

> What's the difference between importing some hundred's of lines from thiserror in rust vs importing the "error" package in go?

Again, you're comparing apples and oranges. It seems you didn't see my previous example, here it is again:

    type errorString string

    func (e errorString) Error() string {
       return string(e)
    }
You've did notice the file you've linked to only has 7 non-comment/documentation lines, right?

Now, to be fair, there are 50 or so lines of code in other files, but I would still love to see a useful Go package which is that small.

I do it frequently. It is indeed easy