Variable trong Ruby tham chiếu tới object. Assignment đổi reference; mutation đổi chính object.
1 | name = "Hudson" |
String mutable nên hai variable cùng thấy thay đổi.
Rebinding không phải mutation
1 | name = "Hudson" |
+ tạo string mới rồi rebind name. Object cũ vẫn được alias_name giữ.
Kiểm tra identity:
1 | name.object_id |
Object thường immutable trong thực hành
Integer, symbol, nil, true, false không mutate theo cách String/Array/Hash làm:
1 | number = 1 |
freeze ngăn mutation trực tiếp:
1 | config = { timeout: 5 }.freeze |
Nhưng freeze là shallow:
1 | config = { headers: {} }.freeze |
Muốn deep immutability cần freeze recursively hoặc dùng data structure/API phù hợp.
Bang method không phải luật mutation tuyệt đối
Nhiều bang method mutate receiver:
1 | name.upcase! |
Nhưng convention ! thường nghĩa “phiên bản nguy hiểm hơn so với method cùng tên”, không phải mọi method mutate đều có !. Ví dụ Array#push vẫn mutate.
Performance
Mutation có thể giảm allocation, nhưng shared mutable state tăng coupling, race condition và bug khó đoán. Immutable update thường dễ reason hơn nhưng có thể tạo object mới.
Đừng kết luận “Ruby ăn RAM vì string immutable/mutable” bằng vài object_id. Memory còn phụ thuộc allocator, GC, retained objects, copy-on-write và workload. Benchmark bằng tool như memory_profiler, stackprof, heap dump—không benchmark bằng cảm xúc khi laptop bật quạt.
Kết luận
Khi truyền object qua nhiều layer, hãy làm rõ ownership:
- method có mutate argument không;
- caller có giữ reference khác không;
- có cần
dup, immutable value object hoặc defensive copy không.
Bug mutable state hiếm khi nằm ở cú pháp. Nó nằm ở hai nơi cùng tin rằng object thuộc về mình.