Hacker News new | ask | show | jobs
by adtac 950 days ago
That's what the `func foo() (*T, error)` pattern is for. It's actually better than syntactic sugar for optional values because now you also have a descriptive reason for why the value is nil.

But if you really cannot afford to return more than one bit of information, do `func foo() (*T, bool)`.

1 comments

> a descriptive reason for why the value is nil.

Result<T,E> does this. I forget exactly why Result is actually different from, and in fact superior to, `func foo() (*T, error)` but IIRC it has to do with function composition and concrete vs generic types.

Result<T,E> is in one of two states: It either has value of type T, or error of type E.

(*T, error) is either T (non-nil, nil), or error (nil/undefined, non-nil), or both (non-nil, non-nil), or neither (nil, nil). By convention usually only the first two are used, but 1) not always, 2) if you rely on convention why even have type system, I have conventions in Python.

Leaving aside pattern matching and all other things which make Rust way more ergonomic and harder to misuse, Go simply lacks a proper sum type that can express exactly one of two options and won't let you use it wrong. Errors should have been done this way from the start, all the theory was known and many practical implementations existed.

I don't know much Rust, but wouldn't the analogy to (*T, error) be Result<Option<T>, E>, which has 3 states? Or is that not a common construct?

Because *T could be nil or non-nil, it seems like the analogy would be a nullable type in the Result<>. In Go, (T, error) would only have the states (non-nil, nil) and (non-nil, non-nil) if T is not a pointer. Still, the Result type seems better to me because the type itself is encapsulating all of this (and the error I guess cannot be null).

As others have mentioned, Result is a sum type so you either have a T or an E, there's no situation in which you can get both or neither.

The second part is that it's reified as a single value, so it works just fine as a normal value e.g. you can map a value to a result, or put results in a map, etc... , language doesn't really care.

And then you can build fun utilities around it e.g. a transformer from Iterator<Item=Result<T, E>> to Result<Vec<T>, E> (iterates and collects values until it encounters an error, in which case it aborts and immediately returns said error).

Don’t rely on half remembering how specific languages implement things, try and internalise the fundamentals. Go functions tend to return a tuple which is a product type, while rust’s result type is sum type. Product types contain. Both things (a result and an error) while a sum type contains a result or an error.