Description
Disclaimer: I have knowledge of #20443 and the following proposes a different approach to the same problem
I would like Go to introduce three new builtin functions (as are new
and make
for instance). The idea behind those three functions is to provide Go's runtime with an efficient temporary immutable state for subscript-able objects (i.e. types that support indexing or key addressing t[...]
). The concept is named object freezing and can be extended further to other types.
The functions
The first function is freeze(s Subscriptable)
where Subscriptable
is a documentation-purpose abstract type. It is used to freeze a subscript-able object.
t := make([]int, 10) // Create a slice of ints
t[0] = 10 // fine
freeze(t)
x := t[0] // fine, x = 10
t[1] = 2 // panics !
// optional
// unfreeze(t)
The second function is unfreeze(s Subscriptable)
and is used to unfreeze a frozen object.
// using previously introduced slice t
unfreeze(t)
t[4] = 8 // fine
The third function is frozen(s Subscriptable) bool
and returns whether an object is frozen.
t2 := make([]int, 10)
frozen(t2) // false
freeze(t2)
frozen(t2) // true
// optional
unfreeze(t2)
Freezing rules
Here is a set of rules of object freezing I can think of.
Any non-frozen object is said unfrozen.
A scope "owns the frost" of an object if it successfully froze it
- A frozen object cannot be used as a left value
- this makes freezing recursive
- subscript & assign operation panics the current goroutine
- accessing members (or indices) as r-values is OK
- A frozen object cannot be assigned until it is unfrozen
- A frozen object can be copied without problem
- A frozen object can only be unfrozen within the scope that owns the frost
- Unfreezing a non-scoped-frozen object panics the current goroutine
- Freezing a frozen object doesn't give the current scope ownership of the frost
- But it won't panic the goroutine
- A frozen object is unfrozen before being garbage collected or when it falls out of scope
Rules Demonstration
Here I will demonstrate what it is possible (or not) using the above set of rules (using int slices).
func owns() {
slice := make([]int, 10)
freeze(slice)
unfreeze(slice) // OK, slice was first frozen within this scope
}
func doesntOwn(slice []int) {
unfreeze(slice) // panics if slice was frozen by call er
}
func wontBecomeOwner(slice []int) {
freeze(slice) // won't give the ownership of the frost to this scope if
// the slice was already frozen i.e. ... (won't panic either)
unfreeze(slice) // ... will panic
}
func noLeftValue() {
t := make([]int, 3)
freeze(t)
t[0] = 10 // Panic !
unfreeze(t)
// 3x3 matrix
t2d := make([][]int, 3)
t2d[0] = t
freeze(t2d)
t2d[0][2] = 4 // Panic !
}
I know this is far from a complete / as accurate as possible proposal, but I hope it will give some insights into what I have been thinking about for a while. Feel free to point at possible loopholes I might have forgotten about !
Notes:
- Of course all I have demonstrated using slices is applicable to maps, arrays and derived types of these (string, and custom types)