{"action":"created","issue":{"url":"https://api.github.com/repos/golang/go/issues/20443","repository_url":"https://api.github.com/repos/golang/go","labels_url":"https://api.github.com/repos/golang/go/issues/20443/labels{/name}","comments_url":"https://api.github.com/repos/golang/go/issues/20443/comments","events_url":"https://api.github.com/repos/golang/go/issues/20443/events","html_url":"https://github.com/golang/go/issues/20443","id":230238277,"node_id":"MDU6SXNzdWUyMzAyMzgyNzc=","number":20443,"title":"proposal: spec: add read-only slices and maps as function arguments","user":{"login":"henryas","id":14799603,"node_id":"MDQ6VXNlcjE0Nzk5NjAz","avatar_url":"https://avatars2.githubusercontent.com/u/14799603?v=4","gravatar_id":"","url":"https://api.github.com/users/henryas","html_url":"https://github.com/henryas","followers_url":"https://api.github.com/users/henryas/followers","following_url":"https://api.github.com/users/henryas/following{/other_user}","gists_url":"https://api.github.com/users/henryas/gists{/gist_id}","starred_url":"https://api.github.com/users/henryas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/henryas/subscriptions","organizations_url":"https://api.github.com/users/henryas/orgs","repos_url":"https://api.github.com/users/henryas/repos","events_url":"https://api.github.com/users/henryas/events{/privacy}","received_events_url":"https://api.github.com/users/henryas/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":232,"closed_issues":666,"state":"open","created_at":"2015-10-24T01:40:49Z","updated_at":"2018-10-26T15:25:59Z","due_on":null,"closed_at":null},"comments":54,"created_at":"2017-05-21T18:22:14Z","updated_at":"2018-10-26T16:55:11Z","closed_at":null,"author_association":"NONE","body":"Background\r\n---\r\nI recently stumbled upon a bug and after hours of searching, I managed to find the culprit, which is a function that accidentally modifies the slice argument passed to it. The problem is that as I took several subsets of the original slice and gave them new identities, I forgot that they were still pointing to the original slice. I don't think this is an uncommon issue, and hence the proposal below.\r\n\r\nProposal\r\n---\r\nI would like to suggest the idea of defining a read-only slice and map in the function arguments. For example, we can define a function as follows:\r\n\r\n//function definition\r\nfunc MyFunction(const mySlice []byte, const myMap map[string][]byte) []byte\r\n\r\n//usage\r\nmySlice := []byte {0x00, 0x01} //normal slice definition. There is no weird _const_ whatsoever.\r\nmyMap := make(map[string][]byte) //normal map definition.\r\n\r\n//you are still using slice and map as you normally would. Slice and map are still modifiable, \r\n//but MyFunction cannot alter the original variables.\r\nMyFunction(mySlice, myMap) \r\n\r\n//you can still make changes here\r\nmyMap[\"Me\"] = 0x01\r\n\r\nor in the interface definition:\r\n\r\n`type MyInterface {\r\n    MyFunction(const mySlice []byte) []byte\r\n}`\r\n\r\nWhy slices and maps?\r\n---\r\nWith structs, interfaces and built-in types, it is easy to tell whether you want them to be modifiable or not. With slices and maps, it is not so easy to tell. Maps are probably less prone to accidental changes, but slices are tricky, especially when they get passed around many functions.\r\n\r\nImplementation Options\r\n---\r\nRead-only slices and maps are shallow-copied upon being passed to the function. Hence, any accidental change is local to the function. I understand that this will not prevent interior mutability, but exterior mutability is good enough. I think this implementation option is probably less intrusive and there are fewer breaking changes to existing codes (if any) - possibly none.\r\n\r\nAlternatively, we may have a more elaborate system where the compiler will not compile if a function tries to modify a read-only slice/map. However, this leads to a very complex solution. What if a new sub-slice is taken from an existing read-only slice, should it be immutable too?  Now, what if that sub-slice is passed into another function, should the compiler check whether the other function preserves the integrity of the sub-slice? \r\n\r\nI personally tend to lean with the first option.   \r\n\r\nSyntax\r\n---\r\nIn order not to add any new keyword, I am thinking of reusing _const_, and it is also more intuitive to those familiar with C-family programming languages. However, I am open to suggestions.\r\n\r\n---\r\n\r\nLet me know what you think.\r\n\r\nThanks"},"comment":{"url":"https://api.github.com/repos/golang/go/issues/comments/433474282","html_url":"https://github.com/golang/go/issues/20443#issuecomment-433474282","issue_url":"https://api.github.com/repos/golang/go/issues/20443","id":433474282,"node_id":"MDEyOklzc3VlQ29tbWVudDQzMzQ3NDI4Mg==","user":{"login":"EmperorEarth","id":7605016,"node_id":"MDQ6VXNlcjc2MDUwMTY=","avatar_url":"https://avatars2.githubusercontent.com/u/7605016?v=4","gravatar_id":"","url":"https://api.github.com/users/EmperorEarth","html_url":"https://github.com/EmperorEarth","followers_url":"https://api.github.com/users/EmperorEarth/followers","following_url":"https://api.github.com/users/EmperorEarth/following{/other_user}","gists_url":"https://api.github.com/users/EmperorEarth/gists{/gist_id}","starred_url":"https://api.github.com/users/EmperorEarth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/EmperorEarth/subscriptions","organizations_url":"https://api.github.com/users/EmperorEarth/orgs","repos_url":"https://api.github.com/users/EmperorEarth/repos","events_url":"https://api.github.com/users/EmperorEarth/events{/privacy}","received_events_url":"https://api.github.com/users/EmperorEarth/received_events","type":"User","site_admin":false},"created_at":"2018-10-26T16:55:11Z","updated_at":"2018-10-26T16:55:11Z","author_association":"NONE","body":"Ignoring syntax for a moment, I'm strongly in favor of immutable maps (slight adjustment on read-only).\r\n\r\nSome notes before I come back and collect and organize my thoughts:\r\n\r\nRelevant opposing Go thoughts (contradictory ideas are good, and realistic, imo):\r\nDon't hide work. Make it obvious to the Gopher how much work every statement requires\r\nDo less work\r\nExpose a synchronous API. Let the caller decide to add asynchronicity depending on their use case\r\n\r\nLet's talk a use case. Suppose we have functions `A()`, `B()`, and `C()`. If `B()` and `C()` require the same two slices from `A()`, there's no way for `C()` to work on these two slices with atomic confidence that its parents and siblings, `A()` and `B()`, aren't changing them. A lock declared at `C()` does nothing because `A()` and `B()` don't know about it. A lock at `A()` requires that `B()`, which could be third-party, agrees to brittle documentation/comments as a contract. So now the coder of `A()` has to keep abreast of `B()` and `C()` or make a separate copy for each before passing it as a param, which is the way I've been doing it. As the caller, it's usable but as the callee, `B()` and `C()`, now you must be aware of how `A()` operates, again.\r\n\r\nI don't mind (and often prefer) the verbosity of \"showing your work\". But from the callee's perspective, having to read into the caller's code how to concurrently manipulate params is too much work, especially if there are potentially dozens of callers. Much better experience to just have immutable slices."}}
{"actor":{"display_login":"EmperorEarth"}}