RubyMiddleTechnical
В чём разница между clone и dup в Ruby?
clone копирует объект вместе с singleton-методами и сохраняет frozen-статус; dup создаёт размороженную копию без singleton-методов. Оба делают shallow copy.
Что общего у clone и dup
Оба метода создают поверхностную (shallow) копию объекта: копируется сам объект, но вложенные объекты (Array, Hash, другие инстансы) не клонируются — они разделяются между оригиналом и копией по ссылке.
a = ['hello', 'world']
b = a.dup
b.push('!')
puts a.inspect # => ["hello", "world"] — массив не затронут
b[0].upcase! # мутируем строку внутри
puts a[0] # => "HELLO" — строка общая!
Ключевые различия
1. frozen?
orig = 'immutable'.freeze
cloned = orig.clone
duped = orig.dup
puts cloned.frozen? # => true (clone сохраняет frozen)
puts duped.frozen? # => false (dup создаёт размороженную копию)
duped << ' appended' # OK
cloned << ' appended' # FrozenError
2. Singleton-методы
obj = Object.new
def obj.greet = 'hello'
cloned = obj.clone
duped = obj.dup
puts cloned.greet # => "hello" — singleton-метод скопирован
puts duped.greet # => NoMethodError — dup не копирует singleton
3. initialize_copy
Оба вызывают initialize_copy на новом объекте. Переопределив этот метод, можно добавить deep copy для нужных полей:
class Cart
attr_reader :items
def initialize
@items = []
end
def initialize_copy(source)
super
@items = source.items.map(&:dup) # deep copy элементов
end
end
cart1 = Cart.new
cart1.items << 'apple'
cart2 = cart1.dup
cart2.items << 'banana'
puts cart1.items.inspect # => ["apple"] — не затронут
Когда что использовать
- dup — когда нужна изменяемая копия объекта для безопасного изменения. Это более распространённый вариант.
- clone — когда важно сохранить singleton-методы или frozen-статус. Используется реже; типичный случай — metaprogramming, прототипирование объектов.
Deep copy через Marshal
Для полного глубокого копирования используют Marshal:
deep = Marshal.load(Marshal.dump(original))
Ограничение: не работает с объектами, содержащими IO, Proc, привязки (Binding).
Подводные камни
- Shallow copy по умолчанию — мутация вложенных объектов (строк, массивов, хэшей) затрагивает обе копии. Всегда проверяйте, нужна ли deep copy.
- clone на frozen объектах — клон будет тоже заморожен. Если вы хотите изменяемую копию замороженного объекта — используйте
dup. - Symbol и Integer — эти объекты не дублируются:
:foo.dupвозвращает сам символ (Ruby >= 2.4 бросал TypeError, >= 2.7 возвращает тот же символ). - initialize_copy не вызывают явно — переопределяйте его, но никогда не вызывайте напрямую; этим занимаются
clone/dup. - ActiveRecord — у AR-моделей
dupсоздаёт новую несохранённую запись без id;cloneкопирует id и может привести к конфликтам при сохранении. - Concurrent modification — ни
clone, ниdupне обеспечивают thread-safe снапшот. Для многопоточного кода используйтеMutex.
Common mistakes
- Сводить clone vs dup к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: объектная динамическая модель Ruby, где почти всё является объектом, а методы ищутся через ancestor chain.
- Не отделять validation, authorization, transaction boundary и business logic.
- Менять похожие API местами без учёта семантики ошибок и ownership.
What the interviewer is testing
- Объясняет clone vs dup через конкретную точку lifecycle в Ruby.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.