Гарантированный способ сравнения объектов по идентичности в Ruby

Что такое гарантированный способ сравнить два объекта по их идентичности в Ruby? Учитывая две переменные, я хочу вернуть true, если переменные указывают на один и тот же объект в памяти.

Для большинства объектов Ruby метод equal? сравнивается по идентификатору:

f = g = Object.new
p f.equal? g  # => true

Однако это не работает для всех объектов. Например:

class F
  def ==(obj) false end
  def ===(obj) false end
  def eql?(obj) false end
  def equal?(obj) false end
  def object_id; self end
end

f = g = F.new
p f == g       # => false
p f === g      # => false
p f.eql? g     # => false
p f.equal? g   # => false
p f.object_id == g.object_id  # => false

Что такое надежный/гарантированный способ сравнения двух объектов по идентичности, которые нельзя победить?

Это чисто интеллектуальный вопрос. Ответ на любой вопрос, который начинается с "почему", вероятно, будет "Потому что мне любопытно".

Ответы

Ответ 1

Вы можете захватить несвязанную версию Object#object_id, привязать ее к рассматриваемому объекту и посмотреть, что он говорит. Учитывая ваш класс F с одним добавлением:

class F
  # ...
  def inspect; 'pancakes!' end # Just so we can tell what we have later.
end

Тогда:

>> f = F.new
>> f.object_id
=> pancakes!
>> unbound_object_id = Object.instance_method(:object_id)
>> unbound_object_id.bind(f).call
=> 2153000340
>> ObjectSpace._id2ref(2153000340).inspect
=> "pancakes!"

Конечно, если кто-то открывает Object и заменяет object_id, тогда вам не повезло, но это будет наименьшим из ваших проблем, если кто-то это сделает. Если вы можете захватить unbound_object_id UnboundMethod, прежде чем что-либо еще будет загружено, то не имеет значения, изменит ли кто-либо Object#object_id как ваш unbound_object_id по-прежнему будет оригинальным правильным.

Таким образом, этот раунд взлома дает вам надежный object_id для любого объекта (с учетом вышеописанных предостережений). Теперь вы можете захватить и сравнить идентификаторы объектов, чтобы получить надежное сравнение.

Ответ 2

Используйте BasicObject#equal?. Согласно Ruby documentation:

В отличие от ==, равный? метод никогда не должен быть переопределен подклассами: он используется для определения идентичности объекта (то есть a.equal? ​​(b) iff a - это тот же объект, что и b).

Нужна более сильная гарантия? AFAIK не может получить его, BaseObject#object_id, ObjectSpace._id2ref, Hash#compare_by_identity может быть переопределен или обезьяна исправлена ​​(по крайней мере, это мое личное убеждение).

Ответ 3

Ответ mu слишком короткий, это замечательно, но есть другой способ, которым вы можете это сделать, используя специальную функцию класса Hash:

def compare_by_identity(x, y)
  h = {}.compare_by_identity
  h[x] = 1
  h[y] = 2
  h.keys.size == 1
end

Функция compare_by_identity была добавлена ​​в Ruby 1.9.2, поэтому эта функция не будет работать в более ранних версиях. Я думаю, что му слишком короткий ответ лучше.