proposal: spec: add read-only slices and maps as function arguments #20443
Description
Background
I 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.
Proposal
I 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:
//function definition
func MyFunction(const mySlice []byte, const myMap map[string][]byte) []byte
//usage
mySlice := []byte {0x00, 0x01} //normal slice definition. There is no weird const whatsoever.
myMap := make(map[string][]byte) //normal map definition.
//you are still using slice and map as you normally would. Slice and map are still modifiable,
//but MyFunction cannot alter the original variables.
MyFunction(mySlice, myMap)
//you can still make changes here
myMap["Me"] = 0x01
or in the interface definition:
type MyInterface { MyFunction(const mySlice []byte) []byte }
Why slices and maps?
With 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.
Implementation Options
Read-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.
Alternatively, 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?
I personally tend to lean with the first option.
Syntax
In 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.
Let me know what you think.
Thanks