{"action":"created","issue":{"url":"https://api.github.com/repos/golang/go/issues/19991","repository_url":"https://api.github.com/repos/golang/go","labels_url":"https://api.github.com/repos/golang/go/issues/19991/labels{/name}","comments_url":"https://api.github.com/repos/golang/go/issues/19991/comments","events_url":"https://api.github.com/repos/golang/go/issues/19991/events","html_url":"https://github.com/golang/go/issues/19991","id":221973579,"node_id":"MDU6SXNzdWUyMjE5NzM1Nzk=","number":19991,"title":"proposal: spec: add built-in result type (like Rust, OCaml)","user":{"login":"tarcieri","id":797,"node_id":"MDQ6VXNlcjc5Nw==","avatar_url":"https://avatars2.githubusercontent.com/u/797?v=4","gravatar_id":"","url":"https://api.github.com/users/tarcieri","html_url":"https://github.com/tarcieri","followers_url":"https://api.github.com/users/tarcieri/followers","following_url":"https://api.github.com/users/tarcieri/following{/other_user}","gists_url":"https://api.github.com/users/tarcieri/gists{/gist_id}","starred_url":"https://api.github.com/users/tarcieri/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tarcieri/subscriptions","organizations_url":"https://api.github.com/users/tarcieri/orgs","repos_url":"https://api.github.com/users/tarcieri/repos","events_url":"https://api.github.com/users/tarcieri/events{/privacy}","received_events_url":"https://api.github.com/users/tarcieri/received_events","type":"User","site_admin":false},"labels":[{"id":150880249,"node_id":"MDU6TGFiZWwxNTA4ODAyNDk=","url":"https://api.github.com/repos/golang/go/labels/Go2","name":"Go2","color":"ffaaaa","default":false},{"id":150880200,"node_id":"MDU6TGFiZWwxNTA4ODAyMDA=","url":"https://api.github.com/repos/golang/go/labels/LanguageChange","name":"LanguageChange","color":"ffaaaa","default":false},{"id":373402289,"node_id":"MDU6TGFiZWwzNzM0MDIyODk=","url":"https://api.github.com/repos/golang/go/labels/NeedsInvestigation","name":"NeedsInvestigation","color":"ededed","default":false},{"id":236419512,"node_id":"MDU6TGFiZWwyMzY0MTk1MTI=","url":"https://api.github.com/repos/golang/go/labels/Proposal","name":"Proposal","color":"ededed","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/golang/go/milestones/30","html_url":"https://github.com/golang/go/milestone/30","labels_url":"https://api.github.com/repos/golang/go/milestones/30/labels","id":1373555,"node_id":"MDk6TWlsZXN0b25lMTM3MzU1NQ==","number":30,"title":"Proposal","description":"Proposals that are pending (not yet accepted/rejected).\r\n","creator":{"login":"rsc","id":104030,"node_id":"MDQ6VXNlcjEwNDAzMA==","avatar_url":"https://avatars1.githubusercontent.com/u/104030?v=4","gravatar_id":"","url":"https://api.github.com/users/rsc","html_url":"https://github.com/rsc","followers_url":"https://api.github.com/users/rsc/followers","following_url":"https://api.github.com/users/rsc/following{/other_user}","gists_url":"https://api.github.com/users/rsc/gists{/gist_id}","starred_url":"https://api.github.com/users/rsc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rsc/subscriptions","organizations_url":"https://api.github.com/users/rsc/orgs","repos_url":"https://api.github.com/users/rsc/repos","events_url":"https://api.github.com/users/rsc/events{/privacy}","received_events_url":"https://api.github.com/users/rsc/received_events","type":"User","site_admin":false},"open_issues":238,"closed_issues":604,"state":"open","created_at":"2015-10-24T01:40:49Z","updated_at":"2018-09-25T20:54:30Z","due_on":null,"closed_at":null},"comments":73,"created_at":"2017-04-15T21:24:18Z","updated_at":"2018-09-25T20:56:52Z","closed_at":null,"author_association":"NONE","body":"This is a proposal to add a [Result Type](https://en.wikipedia.org/wiki/Result_type) to Go. Result types typically contain either a returned value or an error, and could provide first-class encapsulation of the common `(value, err)` pattern ubiquitous throughout Go programs.\r\n\r\nMy apologies if something like this has been submitted before, but hopefully this is a fairly comprehensive writeup of the idea.\r\n\r\n## Background\r\n\r\nSome background on this idea can be found in the post [Error Handling in Go](https://www.innoq.com/en/blog/golang-errors-monads/), although where that post suggests the implementation leverage generics, I will propose that it doesn't have to, and that in fact result types could (with some care) be retrofitted to Go both without adding generics and without making any breaking changes to the language itself.\r\n\r\nThat said, I am self-applying the \"Go 2\" label not because this is a breaking change, but because I expect it will be controversial and, to some degree, going against the grain of the language.\r\n\r\nThe [Rust Result type](https://doc.rust-lang.org/std/result/enum.Result.html) provides some precedent. A similar idea can be found in many functional languages, including Haskell's [Either](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Either.html), OCaml's [result](https://ocaml.org/learn/tutorials/error_handling.html#Resulttype), and Scala's [Either](http://www.scala-lang.org/api/2.12.0/scala/util/Either.html). Rust manages errors quite similarly to Go: errors are just values, bubbling them up is handled at each call site as opposed to the spooky-action-at-a-distance of exceptions using non-local jumps, and some work may be needed to convert error types or wrap errors into [error-chains](https://github.com/brson/error-chain).\r\n\r\nWhere Rust uses sum types (see [Go 2 sum types proposal](https://github.com/golang/go/issues/19412)) and generics to implement result types, as a special case core language feature I think a Go result type doesn't need either, and can simply leverage special case compiler magic. This would involve special syntax and special AST nodes much like Go's collection types presently use.\r\n\r\n## Goals\r\n\r\nI believe the addition of a Result Type to Go could have the following positive outcomes:\r\n\r\n1. Reduce error handling boilerplate: this is an extremely common complaint about Go. The `if err != nil { return nil, err }` \"pattern\" (or minor variations thereof) can be seen everywhere in Go programs. This boilerplate adds no value and only serves to make programs much longer.\r\n2. Allows the compiler to reason about results: in Rust, unconsumed results issue a warning. Though there are linting tools for Go to accomplish the same thing, I think it'd be much more valuable for this to be a first-class feature of the compiler. It's also a reasonably simple one to implement and shouldn't adversely affect compiler performance.\r\n3. Error handling combinators (this is the part I feel goes against the grain of the language): If there were a type for results, it could support a number of methods for handling, transforming, and consuming results. I'll admit this approach comes with a bit of a learning curve, and as such can negatively impact the clarity of programs for people who are unfamiliar with combinator idioms. Though personally I love combinators for error handling, I can definitely see how culturally they may be a bad fit for Go.\r\n\r\n## Syntax Examples\r\n\r\nFirst a quick note: please don't let the idea get too mired in syntax. Syntax is a very easy thing to bikeshed, and I don't think any of these examples serve as the One True Syntax, which is why I'm giving several alternatives.\r\n\r\nInstead I'd prefer people pay attention to the general \"shape\" of the problem, and only look at these examples to better understand the idea.\r\n\r\n### Result type signature\r\n\r\nSimplest thing that works: just add \"result\" in front of the return value tuple:\r\n\r\n```go\r\nfunc f1(arg int) result(int, error) {\r\n```\r\n\r\nMore typical is a \"generic\" syntax, but this should probably be reserved for if/when Go actually adds generics (a result type feature could be adapted to leverage them if that ever happened):\r\n\r\n```go\r\nfunc f1(arg int) result<int, error> {\r\n```\r\n\r\nWhen returning results, we'll need a syntax to wrap values or errors in a result type. This could just be a method invocation:\r\n\r\n```go\r\nreturn result.Ok(value)\r\n```\r\n```go\r\nreturn result.Err(error)\r\n```\r\n\r\nIf we allow \"result\" to be shadowed here, it should avoid breaking any code that already uses \"result\".\r\n\r\nPerhaps \"Go 2\" could add syntax sugar similar to Rust (although it would be a breaking change, I think?):\r\n\r\n```go\r\nreturn Ok(value)\r\n```\r\n```go\r\nreturn Err(value)\r\n```\r\n\r\n### Propagating errors\r\n\r\nRust recently added a `?` operator for propagating errors (see [Rust RFC 243](https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md)). A similar syntax could enable replacing `if err != nil { return _, err }` boilerplate with a shorthand syntax that bubbles the error up the stack.\r\n\r\nHere are some prospective examples. I have only done some cursory checking for syntactic ambiguity. Apologies if these are either ambiguous or breaking changes: I assume with a little work you can find a syntax for this which isn't at breaking change.\r\n\r\nFirst, an example with present-day Go syntax:\r\n\r\n```go\r\ncount, err = fd.Write(bytes)\r\nif err != nil {\r\n    return nil, err\r\n}\r\n```\r\n\r\nNow with a new syntax that consumes a result and bubbles the error up the stack for you. Please keep in mind these examples are only for illustrative purposes:\r\n\r\n```go\r\ncount := fd.Write!(bytes)\r\n```\r\n```go\r\ncount := fd.Write(bytes)!\r\n```\r\n```go\r\ncount := fd.Write?(bytes)\r\n```\r\n```go\r\ncount := fd.Write(bytes)?\r\n```\r\n```go\r\ncount := try(fd.Write(bytes))\r\n```\r\n\r\nNOTE: Rust previously supported the latter, but has generally moved away from it as it isn't chainable.\r\n\r\nIn all of my subsequent examples, I'll be using this syntax, but please note it's just an example, may be ambiguous or have other issues, and I'm certainly not married to it:\r\n\r\n```go\r\ncount := fd.Write(bytes)!\r\n```\r\n\r\n## Backwards compatibility\r\n\r\nThe syntax proposals all use a `result` keyword for identifying the type. I believe (but am certainly not certain) that shadowing rules could be developed that would allow existing code using \"result\" for e.g. a variable name to continue to function as-is without issue.\r\n\r\nIdeally it should be possible to \"upgrade\" existing code to use result types in a completely seamless manner. To do this, we can allow results to be consumed as a 2-tuple, i.e. given:\r\n\r\n```go\r\nfunc f1(arg int) result(int, error) {\r\n```\r\n\r\nIt should be possible to consume it either as:\r\n\r\n```go\r\nresult := f1(42)\r\n```\r\n\r\nor:\r\n\r\n```go\r\n(value, err) := f1(42)\r\n```\r\n\r\nThat is to say, if the compiler sees an assignment from `result(T, E)` to `(T, E)`, it should automatically coerce. This should allow functions to seamlessly switch to using result types.\r\n\r\n## Combinators\r\n\r\nCommonly error handling will be a lot more involved than `if err != nil { return _, err }`. This proposal would be woefully incomplete if that were the only case it helped with.\r\n\r\nResult types are known for being something of a swiss knife of error handling in functional languages due to the \"combinators\" they support. Really these combinators are just a set of methods which allow us to transform and selectively behave based on a result type, typically in \"combination\" with a closure.\r\n\r\n### `Then()`: chain together function calls that return the same result type\r\n\r\nLet's say we had some code that looks like this:\r\n\r\n```go\r\nresp, err := doThing(a)\r\nif err != nil {\r\n    return nil, err\r\n}\r\nresp, err = doAnotherThing(b, resp.foo())\r\nif err != nil {\r\n    return nil, err\r\n}\r\nresp, err = FinishUp(c, resp.bar())\r\nif err != nil {\r\n    return nil, err\r\n}\r\n```\r\n\r\nWith a result type, we can create a function that takes a closure as a parameter and only calls the closure if the result was successful, otherwise short circuiting and returning itself it it represents an error. We'll call this function `Then` (it's described this way in the [Error Handling in Go](https://www.innoq.com/en/blog/golang-errors-monads/)) blog post, and known as `and_then` in Rust). With a function like this, we can rewrite the above example as something like:\r\n\r\n```go\r\nresult := doThing(a).\r\n\tThen(func(resp) { doAnotherThing(b, resp.foo()) }).\r\n\tThen(func(resp) { FinishUp(c, resp.bar()) })\r\n\r\nif result.isError() {\r\n\treturn result.Error()\r\n}\r\n```\r\n\r\nor using one of the proposed syntaxes from above (I'll pick `!` as the magic operator):\r\n\r\n```go\r\nfinal_value := doThing(a).\r\n\tThen(func(resp) { doAnotherThing(b, resp.foo()) }).\r\n\tThen(func(resp) { FinishUp(c, resp.bar()) })!\r\n```\r\n\r\nThis reduces the 12 lines of code in our original example down to three, and leaves us with the final value we're actually after and the result type itself gone from the picture. We never even had to give the result type a name in this case.\r\n\r\nNow granted, the closure syntax in that case feels a little unwieldy/JavaScript-ish. It could probably benefit from a more lightweight closure syntax. I'd personally love something like this:\r\n\r\n```go\r\nfinal_value := doThing(a).\r\n\tThen(|resp| doAnotherThing(b, resp.foo())).\r\n\tThen(|resp| FinishUp(c, resp.bar()))!\r\n```\r\n\r\n...but something like that probably deserves a separate proposal.\r\n\r\n### `Map()` and `MapErr()`: convert between success and error values\r\n\r\nOften times when doing the `if err != nil { return nil, err }` dance you'll want to actually do some handling of the error or transform it to a different type. Something like this:\r\n\r\n```go\r\nresp, err := doThing(a)\r\nif err != nil {\r\n    return nil, myerror.Wrap(err)\r\n}\r\n```\r\n\r\nIn this case, we can accomplish the same thing using `MapErr()` (I'll again use `!` syntax to return the error):\r\n\r\n```go\r\nresp := doThing(a).\r\n\tMapErr(func(err) { myerror.Wrap(err) })!\r\n```\r\n\r\n`Map` does the same thing, just transforming the success value instead of the error.\r\n\r\n### And more!\r\n\r\nThere are many more combinators than the ones I have shown here, but I believe these are the most interesting. For a better idea of what a fully-featured result type looks like, I'd suggest checking out Rust's:\r\n\r\nhttps://doc.rust-lang.org/std/result/enum.Result.html"},"comment":{"url":"https://api.github.com/repos/golang/go/issues/comments/424498800","html_url":"https://github.com/golang/go/issues/19991#issuecomment-424498800","issue_url":"https://api.github.com/repos/golang/go/issues/19991","id":424498800,"node_id":"MDEyOklzc3VlQ29tbWVudDQyNDQ5ODgwMA==","user":{"login":"networkimprov","id":458838,"node_id":"MDQ6VXNlcjQ1ODgzOA==","avatar_url":"https://avatars3.githubusercontent.com/u/458838?v=4","gravatar_id":"","url":"https://api.github.com/users/networkimprov","html_url":"https://github.com/networkimprov","followers_url":"https://api.github.com/users/networkimprov/followers","following_url":"https://api.github.com/users/networkimprov/following{/other_user}","gists_url":"https://api.github.com/users/networkimprov/gists{/gist_id}","starred_url":"https://api.github.com/users/networkimprov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/networkimprov/subscriptions","organizations_url":"https://api.github.com/users/networkimprov/orgs","repos_url":"https://api.github.com/users/networkimprov/repos","events_url":"https://api.github.com/users/networkimprov/events{/privacy}","received_events_url":"https://api.github.com/users/networkimprov/received_events","type":"User","site_admin":false},"created_at":"2018-09-25T20:56:52Z","updated_at":"2018-09-25T20:56:52Z","author_association":"NONE","body":"Related:\r\n\r\n\"Draft Designs\" for new error handling features:\r\nhttps://go.googlesource.com/proposal/+/master/design/go2draft.md\r\n\r\nFeedback re the errors design:\r\nhttps://github.com/golang/go/wiki/Go2ErrorHandlingFeedback"}}
{"actor":{"display_login":"networkimprov"}}