3 changed files with 40 additions and 721 deletions
@ -1,683 +0,0 @@ |
|||||||
# Go Reference Type Simplification - Revised Proposal |
|
||||||
|
|
||||||
## Executive Summary |
|
||||||
|
|
||||||
Keep Go's convenient syntax (slicing, `<-`, `for range`) while making reference semantics **explicit through pointer types**. This reduces cognitive load and improves safety without sacrificing ergonomics. |
|
||||||
|
|
||||||
## Core Principle: Explicit Pointers, Convenient Syntax |
|
||||||
|
|
||||||
**The Key Insight:** |
|
||||||
- Make slices/maps/channels explicitly `*[]T`, `*map[K]V`, `*chan T` |
|
||||||
- Keep convenient operators (auto-dereference like struct methods do) |
|
||||||
- Eliminate special allocation functions (`make()`) |
|
||||||
- Add explicit control where it matters (grow, clone) |
|
||||||
|
|
||||||
## Proposed Changes |
|
||||||
|
|
||||||
### 1. Slices Become `*[]T` (Explicit Pointers) |
|
||||||
|
|
||||||
**Current Problem:** |
|
||||||
```go |
|
||||||
s := []int{1, 2, 3} // Looks like value, is reference |
|
||||||
s2 := s // Copies reference - HIDDEN SHARING |
|
||||||
s2[0] = 99 // Mutates s too! Not obvious |
|
||||||
``` |
|
||||||
|
|
||||||
**Proposed:** |
|
||||||
```go |
|
||||||
s := &[]int{1, 2, 3} // Explicit pointer allocation |
|
||||||
s2 := s // Copies pointer - OBVIOUS SHARING |
|
||||||
s2[0] = 99 // Mutates s too - but now obvious! |
|
||||||
|
|
||||||
// Slicing still works (auto-dereference) |
|
||||||
sub := s[1:3] // Returns *[]int (new slice header, same backing) |
|
||||||
sub := s[1:3:5] // Full slicing with capacity still works |
|
||||||
|
|
||||||
// To copy data, be explicit |
|
||||||
s3 := s.Clone() // Deep copy |
|
||||||
s3 := &[]int(*s) // Alternative: copy via literal |
|
||||||
|
|
||||||
// Append works as before |
|
||||||
s.Append(4, 5, 6) // Implicit grow if needed (fine!) |
|
||||||
s.Grow(100) // Explicit capacity increase |
|
||||||
``` |
|
||||||
|
|
||||||
**What Changes:** |
|
||||||
- ✅ Allocation: `&[]T{}` instead of `make([]T, len, cap)` |
|
||||||
- ✅ Type: `*[]int` instead of `[]int` |
|
||||||
- ✅ Explicit clone: Must call `.Clone()` to copy data |
|
||||||
- ✅ Explicit grow: `.Grow(n)` for pre-allocation |
|
||||||
- ❌ Slicing syntax: **KEEP IT** - `s[i:j]` still works |
|
||||||
- ❌ Append behavior: **KEEP IT** - implicit growth is fine |
|
||||||
- ❌ Auto-dereference: Like methods, `s[i]` auto-derefs |
|
||||||
|
|
||||||
**Benefits:** |
|
||||||
- Assignment `s2 := s` is obviously pointer copy |
|
||||||
- Function parameters `func f(s *[]int)` show mutation potential |
|
||||||
- Still convenient: slicing and indexing work as before |
|
||||||
|
|
||||||
### 2. Maps Become `*map[K]V` (Explicit Pointers) |
|
||||||
|
|
||||||
**Current Problem:** |
|
||||||
```go |
|
||||||
m := make(map[string]int) // Special make() function |
|
||||||
m2 := m // HIDDEN reference sharing |
|
||||||
|
|
||||||
var m3 map[string]int // nil map |
|
||||||
v := m3["key"] // OK - returns zero value |
|
||||||
m3["key"] = 42 // PANIC! Nil map write trap |
|
||||||
``` |
|
||||||
|
|
||||||
**Proposed:** |
|
||||||
```go |
|
||||||
m := &map[string]int{} // Explicit pointer allocation |
|
||||||
m := &map[string]int{ // Literal initialization |
|
||||||
"key": 42, |
|
||||||
} |
|
||||||
|
|
||||||
m2 := m // Obviously copies pointer |
|
||||||
|
|
||||||
// Map operations auto-dereference |
|
||||||
m["key"] = 42 // Auto-deref (like s[i] for slices) |
|
||||||
v := m["key"] |
|
||||||
v, ok := m["key"] |
|
||||||
|
|
||||||
// Nil pointer is consistent |
|
||||||
var m3 *map[string]int // nil pointer |
|
||||||
v := m3["key"] // PANIC - nil pointer deref (consistent!) |
|
||||||
m3 = &map[string]int{} // Must allocate |
|
||||||
m3["key"] = 42 // Now OK |
|
||||||
|
|
||||||
// Copying requires explicit clone |
|
||||||
m4 := m.Clone() // Deep copy |
|
||||||
``` |
|
||||||
|
|
||||||
**What Changes:** |
|
||||||
- ✅ Allocation: `&map[K]V{}` instead of `make(map[K]V)` |
|
||||||
- ✅ Type: `*map[K]V` instead of `map[K]V` |
|
||||||
- ✅ Nil behavior: Consistent nil pointer panic |
|
||||||
- ✅ Explicit clone: Must call `.Clone()` |
|
||||||
- ❌ Map syntax: **KEEP IT** - `m[k]` auto-derefs |
|
||||||
|
|
||||||
**Benefits:** |
|
||||||
- Obvious pointer semantics |
|
||||||
- No special nil-map read-only trap |
|
||||||
- Clear when data is shared |
|
||||||
|
|
||||||
### 3. Channels Become `*chan T` (Explicit Pointers) |
|
||||||
|
|
||||||
**Current Problem:** |
|
||||||
```go |
|
||||||
ch := make(chan int, 10) // Special make() function |
|
||||||
ch2 := ch // HIDDEN reference sharing |
|
||||||
|
|
||||||
var ch3 chan int // nil channel |
|
||||||
ch3 <- 42 // BLOCKS FOREVER! Silent deadlock trap |
|
||||||
``` |
|
||||||
|
|
||||||
**Proposed:** |
|
||||||
```go |
|
||||||
ch := &chan int{cap: 10} // Explicit pointer allocation |
|
||||||
ch := &chan int{} // Unbuffered (cap: 0) |
|
||||||
|
|
||||||
ch2 := ch // Obviously copies pointer |
|
||||||
|
|
||||||
// Channel operations auto-dereference |
|
||||||
ch <- 42 // KEEP <- syntax! |
|
||||||
v := <-ch |
|
||||||
v, ok := <-ch |
|
||||||
|
|
||||||
// for range still works |
|
||||||
for v := range ch { // KEEP for range! |
|
||||||
process(v) |
|
||||||
} |
|
||||||
|
|
||||||
// select still works |
|
||||||
select { // KEEP select! |
|
||||||
case v := <-ch: |
|
||||||
handle(v) |
|
||||||
case ch2 <- 42: |
|
||||||
sent() |
|
||||||
} |
|
||||||
|
|
||||||
// Nil pointer is consistent |
|
||||||
var ch3 *chan int // nil pointer |
|
||||||
ch3 <- 42 // PANIC - nil pointer deref (consistent!) |
|
||||||
|
|
||||||
// Directional channels as type aliases or interfaces |
|
||||||
type SendOnly[T any] = *chan T // Could restrict at type level |
|
||||||
func send(ch *chan int) {} // Or just document convention |
|
||||||
``` |
|
||||||
|
|
||||||
**What Changes:** |
|
||||||
- ✅ Allocation: `&chan T{cap: n}` instead of `make(chan T, n)` |
|
||||||
- ✅ Type: `*chan T` instead of `chan T` |
|
||||||
- ✅ Nil behavior: Consistent nil pointer panic |
|
||||||
- ❌ Send/receive: **KEEP `<-` syntax** |
|
||||||
- ❌ Select: **KEEP `select` statement** |
|
||||||
- ❌ For range: **KEEP `for range ch`** |
|
||||||
|
|
||||||
**Benefits:** |
|
||||||
- Obvious pointer semantics |
|
||||||
- No silent nil-channel blocking trap |
|
||||||
- Keep all the convenient syntax |
|
||||||
- Directional types could be interfaces if needed |
|
||||||
|
|
||||||
### 4. Unified Allocation: Eliminate `make()` |
|
||||||
|
|
||||||
**Before (Three Allocation Primitives):** |
|
||||||
```go |
|
||||||
new(T) // Returns *T (zero value) |
|
||||||
make([]T, len, cap) // Returns []T (special) |
|
||||||
make(map[K]V, hint) // Returns map[K]V (special) |
|
||||||
make(chan T, buf) // Returns chan T (special) |
|
||||||
``` |
|
||||||
|
|
||||||
**After (One Allocation Syntax):** |
|
||||||
```go |
|
||||||
new(T) // Returns *T (zero value) |
|
||||||
&T{} // Returns *T (composite literal) |
|
||||||
&[]T{} // Returns *[]T (empty slice) |
|
||||||
&[n]T{} // Returns *[n]T (array) |
|
||||||
&map[K]V{} // Returns *map[K]V (empty map) |
|
||||||
&chan T{} // Returns *chan T (unbuffered) |
|
||||||
&chan T{cap: 10} // Returns *chan T (buffered) |
|
||||||
``` |
|
||||||
|
|
||||||
**Eliminate:** |
|
||||||
- ❌ `make()` entirely |
|
||||||
- ❌ Special capacity/hint parameters (use methods instead) |
|
||||||
|
|
||||||
### 5. Type System Unification |
|
||||||
|
|
||||||
**Before:** |
|
||||||
``` |
|
||||||
Value types: int, float, bool, struct, [N]T |
|
||||||
Reference types: []T, map[K]V, chan T (SPECIAL SEMANTICS) |
|
||||||
Pointer types: *T |
|
||||||
``` |
|
||||||
|
|
||||||
**After:** |
|
||||||
``` |
|
||||||
Value types: int, float, bool, struct, [N]T |
|
||||||
Pointer types: *T (including *[]T, *map[K]V, *chan T - UNIFIED) |
|
||||||
``` |
|
||||||
|
|
||||||
All pointer types have consistent semantics: |
|
||||||
- Assignment copies the pointer |
|
||||||
- Nil pointer dereference panics consistently |
|
||||||
- Auto-dereference for convenient syntax |
|
||||||
- Explicit `.Clone()` for deep copy |
|
||||||
|
|
||||||
## Syntax Comparison |
|
||||||
|
|
||||||
### Slices |
|
||||||
|
|
||||||
**Before:** |
|
||||||
```go |
|
||||||
// Many ways to create |
|
||||||
var s []int // nil slice |
|
||||||
s = []int{} // empty slice |
|
||||||
s = make([]int, 10) // len=10, cap=10 |
|
||||||
s = make([]int, 10, 20) // len=10, cap=20 |
|
||||||
s = []int{1, 2, 3} // literal |
|
||||||
|
|
||||||
// Slicing |
|
||||||
sub := s[1:3] // subslice |
|
||||||
sub = s[:3] // from start |
|
||||||
sub = s[1:] // to end |
|
||||||
sub = s[:] // full slice |
|
||||||
sub = s[1:3:5] // with capacity |
|
||||||
|
|
||||||
// Append |
|
||||||
s = append(s, 4) // might reallocate |
|
||||||
s = append(s, items...) // spread |
|
||||||
|
|
||||||
// Copy (manual) |
|
||||||
s2 := make([]int, len(s)) |
|
||||||
copy(s2, s) |
|
||||||
``` |
|
||||||
|
|
||||||
**After:** |
|
||||||
```go |
|
||||||
// One way to create |
|
||||||
var s *[]int // nil pointer |
|
||||||
s = &[]int{} // empty slice |
|
||||||
s = &[10]int{}[:] // len=10 from array |
|
||||||
s = &[]int{1, 2, 3} // literal |
|
||||||
|
|
||||||
// Slicing (UNCHANGED) |
|
||||||
sub := s[1:3] // auto-deref, returns *[]int |
|
||||||
sub = s[:3] |
|
||||||
sub = s[1:] |
|
||||||
sub = s[:] |
|
||||||
sub = s[1:3:5] |
|
||||||
|
|
||||||
// Append (UNCHANGED) |
|
||||||
s.Append(4) // might reallocate (fine!) |
|
||||||
s.Append(items...) // spread |
|
||||||
|
|
||||||
// Explicit operations |
|
||||||
s.Grow(100) // pre-allocate capacity |
|
||||||
s2 := s.Clone() // explicit deep copy |
|
||||||
``` |
|
||||||
|
|
||||||
### Maps |
|
||||||
|
|
||||||
**Before:** |
|
||||||
```go |
|
||||||
// Many ways to create |
|
||||||
var m map[K]V // nil map |
|
||||||
m = map[K]V{} // empty map |
|
||||||
m = make(map[K]V) // empty map |
|
||||||
m = make(map[K]V, 100) // with hint |
|
||||||
m = map[K]V{k: v} // literal |
|
||||||
|
|
||||||
// Access |
|
||||||
m[k] = v |
|
||||||
v = m[k] |
|
||||||
v, ok = m[k] |
|
||||||
|
|
||||||
// Copy (manual) |
|
||||||
m2 := make(map[K]V, len(m)) |
|
||||||
for k, v := range m { |
|
||||||
m2[k] = v |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**After:** |
|
||||||
```go |
|
||||||
// One way to create |
|
||||||
var m *map[K]V // nil pointer |
|
||||||
m = &map[K]V{} // empty map |
|
||||||
m = &map[K]V{k: v} // literal |
|
||||||
|
|
||||||
// Access (UNCHANGED) |
|
||||||
m[k] = v // auto-deref |
|
||||||
v = m[k] |
|
||||||
v, ok = m[k] |
|
||||||
|
|
||||||
// Explicit operations |
|
||||||
m2 := m.Clone() // explicit deep copy |
|
||||||
``` |
|
||||||
|
|
||||||
### Channels |
|
||||||
|
|
||||||
**Before:** |
|
||||||
```go |
|
||||||
// Create |
|
||||||
ch := make(chan int) // unbuffered |
|
||||||
ch := make(chan int, 10) // buffered |
|
||||||
|
|
||||||
// Operations |
|
||||||
ch <- 42 // send |
|
||||||
v := <-ch // receive |
|
||||||
v, ok := <-ch // receive with closed check |
|
||||||
close(ch) |
|
||||||
|
|
||||||
// for range |
|
||||||
for v := range ch { |
|
||||||
process(v) |
|
||||||
} |
|
||||||
|
|
||||||
// select |
|
||||||
select { |
|
||||||
case v := <-ch: |
|
||||||
handle(v) |
|
||||||
case <-timeout: |
|
||||||
timeout() |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**After:** |
|
||||||
```go |
|
||||||
// Create |
|
||||||
ch := &chan int{} // unbuffered |
|
||||||
ch := &chan int{cap: 10} // buffered |
|
||||||
|
|
||||||
// Operations (UNCHANGED) |
|
||||||
ch <- 42 // auto-deref |
|
||||||
v := <-ch |
|
||||||
v, ok := <-ch |
|
||||||
ch.Close() // method instead of builtin |
|
||||||
|
|
||||||
// for range (UNCHANGED) |
|
||||||
for v := range ch { |
|
||||||
process(v) |
|
||||||
} |
|
||||||
|
|
||||||
// select (UNCHANGED) |
|
||||||
select { |
|
||||||
case v := <-ch: |
|
||||||
handle(v) |
|
||||||
case <-timeout: |
|
||||||
timeout() |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Grammar Simplification |
|
||||||
|
|
||||||
### Eliminated Syntax |
|
||||||
|
|
||||||
1. **`make()` builtin** - 3 different forms → 0 |
|
||||||
- `make([]T, n, cap)` → `&[]T{}` + `.Grow(cap)` |
|
||||||
- `make(map[K]V, hint)` → `&map[K]V{}` |
|
||||||
- `make(chan T, buf)` → `&chan T{cap: buf}` |
|
||||||
|
|
||||||
2. **Dual allocation semantics** - 2 primitives → 1 |
|
||||||
- `new(T)` and `make(T)` → just `new(T)` or `&T{}` |
|
||||||
|
|
||||||
### Preserved Syntax |
|
||||||
|
|
||||||
1. ✅ Slice expressions: `s[i:j]`, `s[i:j:k]` |
|
||||||
2. ✅ Channel operators: `<-ch`, `ch<-` |
|
||||||
3. ✅ Select statement: `select { case ... }` |
|
||||||
4. ✅ Range over channels: `for v := range ch` |
|
||||||
5. ✅ Map/slice indexing: `m[k]`, `s[i]` |
|
||||||
6. ✅ Auto-dereference: Like methods on `*T` |
|
||||||
|
|
||||||
## New Built-in Methods |
|
||||||
|
|
||||||
### Slices (`*[]T`) |
|
||||||
|
|
||||||
```go |
|
||||||
s := &[]int{1, 2, 3} |
|
||||||
|
|
||||||
// Capacity management |
|
||||||
s.Grow(n int) // Ensure capacity for n more elements |
|
||||||
s.Cap() int // Current capacity |
|
||||||
s.Len() int // Current length |
|
||||||
|
|
||||||
// Modification |
|
||||||
s.Append(items ...T) // Append items (implicit grow OK) |
|
||||||
s.Insert(i int, items ...T) // Insert at index |
|
||||||
s.Delete(i, j int) // Delete s[i:j] |
|
||||||
s.Clear() // Set length to 0 |
|
||||||
|
|
||||||
// Copying |
|
||||||
s.Clone() *[]T // Deep copy |
|
||||||
s.Slice(i, j int) *[]T // Alternative to s[i:j] |
|
||||||
``` |
|
||||||
|
|
||||||
### Maps (`*map[K]V`) |
|
||||||
|
|
||||||
```go |
|
||||||
m := &map[string]int{} |
|
||||||
|
|
||||||
// Capacity |
|
||||||
m.Len() int // Number of keys |
|
||||||
|
|
||||||
// Modification |
|
||||||
m.Clear() // Remove all keys |
|
||||||
m.Delete(k K) // Delete key |
|
||||||
|
|
||||||
// Copying |
|
||||||
m.Clone() *map[K]V // Deep copy |
|
||||||
|
|
||||||
// Bulk operations |
|
||||||
m.Keys() *[]K // All keys |
|
||||||
m.Values() *[]V // All values |
|
||||||
m.Merge(other *map[K]V) // Merge other into m |
|
||||||
``` |
|
||||||
|
|
||||||
### Channels (`*chan T`) |
|
||||||
|
|
||||||
```go |
|
||||||
ch := &chan int{cap: 10} |
|
||||||
|
|
||||||
// Metadata |
|
||||||
ch.Len() int // Items in buffer |
|
||||||
ch.Cap() int // Buffer capacity |
|
||||||
|
|
||||||
// Control |
|
||||||
ch.Close() // Close channel (method vs builtin) |
|
||||||
``` |
|
||||||
|
|
||||||
## Auto-Dereference Rules |
|
||||||
|
|
||||||
Like struct methods today, pointer types auto-dereference: |
|
||||||
|
|
||||||
```go |
|
||||||
type Person struct { name string } |
|
||||||
func (p *Person) Name() string { return p.name } |
|
||||||
|
|
||||||
p := &Person{name: "Alice"} |
|
||||||
n := p.Name() // Auto-deref: (*p).Name() |
|
||||||
|
|
||||||
// Same for new pointer types |
|
||||||
s := &[]int{1, 2, 3} |
|
||||||
v := s[0] // Auto-deref: (*s)[0] |
|
||||||
sub := s[1:3] // Auto-deref: (*s)[1:3] |
|
||||||
|
|
||||||
m := &map[K]V{} |
|
||||||
v = m[k] // Auto-deref: (*m)[k] |
|
||||||
|
|
||||||
ch := &chan int{} |
|
||||||
ch <- 42 // Auto-deref: (*ch) <- 42 |
|
||||||
v = <-ch // Auto-deref: <-(*ch) |
|
||||||
``` |
|
||||||
|
|
||||||
**Rule:** Pointer to slice/map/channel auto-derefs for indexing, slicing, and channel ops. |
|
||||||
|
|
||||||
## Concurrency Safety |
|
||||||
|
|
||||||
### Before: Implicit Sharing |
|
||||||
|
|
||||||
```go |
|
||||||
func worker(s []int, wg *sync.WaitGroup) { |
|
||||||
defer wg.Done() |
|
||||||
s[0] = 99 // RACE - not obvious from signature |
|
||||||
} |
|
||||||
|
|
||||||
s := []int{1, 2, 3} |
|
||||||
var wg sync.WaitGroup |
|
||||||
wg.Add(2) |
|
||||||
go worker(s, &wg) // Sharing not obvious |
|
||||||
go worker(s, &wg) // Two goroutines mutate same slice |
|
||||||
wg.Wait() |
|
||||||
``` |
|
||||||
|
|
||||||
### After: Explicit Sharing |
|
||||||
|
|
||||||
```go |
|
||||||
func worker(s *[]int, wg *sync.WaitGroup) { |
|
||||||
defer wg.Done() |
|
||||||
(*s)[0] = 99 // RACE - but obvious from *[]int |
|
||||||
} |
|
||||||
|
|
||||||
s := &[]int{1, 2, 3} |
|
||||||
var wg sync.WaitGroup |
|
||||||
wg.Add(2) |
|
||||||
go worker(s, &wg) // OBVIOUS pointer sharing |
|
||||||
go worker(s, &wg) // Clear that both access same data |
|
||||||
wg.Wait() |
|
||||||
``` |
|
||||||
|
|
||||||
**Benefits:** |
|
||||||
- Function signature shows mutation: `func f(s *[]int)` vs `func f(s []int)` |
|
||||||
- Pointer copy is obvious: `s2 := s` (copies pointer) |
|
||||||
- Value copy requires explicit clone: `s2 := s.Clone()` |
|
||||||
|
|
||||||
### Pattern: Immutable by Default |
|
||||||
|
|
||||||
```go |
|
||||||
// Current Go - unclear if mutation happens |
|
||||||
func ProcessSlice(s []int) []int { |
|
||||||
s[0] = 99 // Mutates caller's slice! |
|
||||||
return s |
|
||||||
} |
|
||||||
|
|
||||||
// Proposed - explicit mutation |
|
||||||
func ProcessSlice(s *[]int) { |
|
||||||
(*s)[0] = 99 // Clear mutation |
|
||||||
} |
|
||||||
|
|
||||||
// Or value semantics (copy) |
|
||||||
func ProcessSlice(s []int) []int { // Note: NOT pointer |
|
||||||
result := &[]int(s) // Explicit copy from value |
|
||||||
(*result)[0] = 99 // Mutate copy |
|
||||||
return result |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Migration Path |
|
||||||
|
|
||||||
### Phase 1: Allow Both (Backward Compatible) |
|
||||||
|
|
||||||
```go |
|
||||||
// Old style still works |
|
||||||
s := []int{1, 2, 3} |
|
||||||
s = append(s, 4) |
|
||||||
|
|
||||||
// New style also works (same runtime behavior) |
|
||||||
s := &[]int{1, 2, 3} |
|
||||||
s.Append(4) |
|
||||||
|
|
||||||
// Add deprecation warnings |
|
||||||
make([]int, 10) // WARNING: Use &[]int{} or &[10]int{}[:] |
|
||||||
``` |
|
||||||
|
|
||||||
### Phase 2: Deprecate Old Forms |
|
||||||
|
|
||||||
```go |
|
||||||
// Compiler warnings |
|
||||||
[]int{1, 2, 3} // WARNING: Use &[]int{1, 2, 3} |
|
||||||
make([]int, 10) // WARNING: Use &[]int{} with .Grow(10) |
|
||||||
make(map[K]V) // WARNING: Use &map[K]V{} |
|
||||||
make(chan T, 10) // WARNING: Use &chan T{cap: 10} |
|
||||||
``` |
|
||||||
|
|
||||||
### Phase 3: Breaking Change |
|
||||||
|
|
||||||
```go |
|
||||||
// Only new syntax allowed |
|
||||||
&[]int{1, 2, 3} // OK |
|
||||||
&map[K]V{} // OK |
|
||||||
&chan T{cap: 10} // OK |
|
||||||
|
|
||||||
[]int{1, 2, 3} // ERROR: Use &[]int{1, 2, 3} |
|
||||||
make([]int, 10) // ERROR: Removed |
|
||||||
``` |
|
||||||
|
|
||||||
## Implementation Impact |
|
||||||
|
|
||||||
### Compiler Changes |
|
||||||
|
|
||||||
**New:** |
|
||||||
- Auto-dereference for `*[]T`, `*map[K]V`, `*chan T` |
|
||||||
- Built-in methods (`.Append()`, `.Clone()`, `.Grow()`, etc.) |
|
||||||
- Composite literal fields: `&chan T{cap: 10}` |
|
||||||
|
|
||||||
**Removed:** |
|
||||||
- `make()` builtin (3 forms) |
|
||||||
- Special case type checking for reference types |
|
||||||
|
|
||||||
**Preserved:** |
|
||||||
- Slice expressions `s[i:j:k]` |
|
||||||
- Channel operators `<-` |
|
||||||
- Select statement |
|
||||||
- Range over channels |
|
||||||
- All runtime implementations |
|
||||||
|
|
||||||
### Runtime Changes |
|
||||||
|
|
||||||
**Minimal:** |
|
||||||
- Same memory layout for slices/maps/channels |
|
||||||
- Same GC behavior |
|
||||||
- Same scheduler |
|
||||||
- No performance impact |
|
||||||
|
|
||||||
**API:** |
|
||||||
- Add runtime functions for `.Clone()`, `.Grow()`, etc. |
|
||||||
- These can be compiler intrinsics for performance |
|
||||||
|
|
||||||
## Complexity Reduction |
|
||||||
|
|
||||||
| Metric | Before | After | Reduction | |
|
||||||
|--------|--------|-------|-----------| |
|
||||||
| **Allocation primitives** | 2 (`new`, `make`) | 1 (`&T{}`) | **50%** | |
|
||||||
| **make() forms** | 3 (slice, map, chan) | 0 | **100%** | |
|
||||||
| **Reference type special cases** | 3 types | 0 (unified) | **100%** | |
|
||||||
| **Nil traps** | 2 (nil map write, nil chan) | 0 (consistent panic) | **100%** | |
|
||||||
| **Type system categories** | 3 (value, ref, ptr) | 2 (value, ptr) | **33%** | |
|
||||||
| **Syntax variants preserved** | Slicing, `<-`, select, range | All kept | **0%** | |
|
||||||
|
|
||||||
**Total complexity reduction: ~30%** while keeping ergonomic syntax. |
|
||||||
|
|
||||||
## Real-World Example: ORLY Codebase |
|
||||||
|
|
||||||
### Before |
|
||||||
|
|
||||||
```go |
|
||||||
// pkg/database/query-events.go |
|
||||||
func QueryEvents(db *badger.DB, filter *filter.T) ([]uint64, error) { |
|
||||||
results := make([]uint64, 0, 1000) |
|
||||||
// ... query logic |
|
||||||
return results, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Caller must handle returned slice |
|
||||||
events, err := QueryEvents(db, f) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
events = append(events, moreEvents...) // Might copy |
|
||||||
``` |
|
||||||
|
|
||||||
### After |
|
||||||
|
|
||||||
```go |
|
||||||
// pkg/database/query-events.go |
|
||||||
func QueryEvents(db *badger.DB, filter *filter.T) (results *[]uint64, err error) { |
|
||||||
results = &[]uint64{} |
|
||||||
results.Grow(1000) // Explicit capacity |
|
||||||
// ... query logic |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Caller gets explicit pointer |
|
||||||
events, err := QueryEvents(db, f) |
|
||||||
if chk.E(err) { |
|
||||||
return |
|
||||||
} |
|
||||||
events.Append(moreEvents...) // Clear mutation |
|
||||||
``` |
|
||||||
|
|
||||||
**Benefits in ORLY:** |
|
||||||
- Clear which functions mutate vs return new data |
|
||||||
- Obvious when slices are shared across goroutines |
|
||||||
- Explicit capacity management for performance-critical code |
|
||||||
- No hidden allocations from append |
|
||||||
|
|
||||||
## Conclusion |
|
||||||
|
|
||||||
### What We Keep |
|
||||||
✅ Slice expressions: `s[1:3:5]` |
|
||||||
✅ Channel operators: `<-` |
|
||||||
✅ Select statement |
|
||||||
✅ For range channels |
|
||||||
✅ Implicit append growth |
|
||||||
✅ Convenient auto-dereference |
|
||||||
|
|
||||||
### What We Gain |
|
||||||
✅ Explicit pointer semantics |
|
||||||
✅ Obvious data sharing |
|
||||||
✅ Consistent nil behavior |
|
||||||
✅ Unified type system |
|
||||||
✅ Simpler language (no `make()`) |
|
||||||
✅ Better concurrency safety |
|
||||||
|
|
||||||
### What We Lose |
|
||||||
❌ `make()` function (replaced by `&T{}`) |
|
||||||
❌ Implicit reference types (now explicit `*[]T`) |
|
||||||
❌ Zero-value usability for maps/slices (must allocate) |
|
||||||
|
|
||||||
### Recommendation |
|
||||||
|
|
||||||
This revision strikes the right balance: |
|
||||||
- **Keep** Go's ergonomic syntax that makes it productive |
|
||||||
- **Add** explicit semantics that make code safer and clearer |
|
||||||
- **Remove** only the truly confusing parts (`make()`, implicit references) |
|
||||||
- **Gain** ~30% complexity reduction without sacrificing convenience |
|
||||||
|
|
||||||
The migration is straightforward and could be done gradually with good tooling support. |
|
||||||
Loading…
Reference in new issue