A Go linter that detects mutations to range loop value copies.
Name origin: "dead mutation" = mutation with no effect
Go's range loop copies values, so modifications to the copy don't affect the original slice:
for _, user := range users {
user.Name = "updated" // Does NOT update the original slice!
}This is:
- A common pitfall for beginners
- Not a compile error
- Not a runtime panic
- A silent bug
| Linter | Detects | Range loop value mutation |
|---|---|---|
| staticcheck SA4005 | Field assignment to value receiver | No |
| copyloopvar | i := i copy pattern |
Different issue |
| ineffassign | Assignment to unused variable | No |
| loopclosure | Capture in goroutine/defer | Different issue |
| deadmut | Mutation to range loop value | Yes |
go install github.com/mickamy/deadmut@latestdeadmut ./...for _, user := range users {
user.Name = "updated" // deadmut: mutation to range value copy has no effect
}for _, user := range users {
user.SetName("updated") // deadmut: pointer receiver method on range value copy has no effect
}for _, user := range users {
updateUser(&user) // deadmut: pointer to range value copy may not have intended effect
}for i := range users {
users[i].Name = "updated" // OK - direct modification
}for _, user := range users { // users is []*User
user.Name = "updated" // OK - modifying via pointer
}for _, user := range users {
user.Name = "updated"
process(user) // OK - intentionally using the copy
}for _, user := range users {
user.Name = "updated"
results = append(results, user) // OK - collecting modified copies
}// Before (bug)
for _, user := range users {
user.Name = "updated"
}
// After (fix 1: use index)
for i := range users {
users[i].Name = "updated"
}
// After (fix 2: use pointer slice)
for _, user := range users { // users is []*User
user.Name = "updated"
}