← voltar aos artigos
14/07/2026Ruby · memória · CRuby

Ruby sob o Capô: Como a Memória e o object_id Funcionam de Verdade

Como Arrays acessam elementos em O(1), por que object_id não é endereço de memória no Ruby moderno e o que mudou do Ruby 2.6 ao 4.0.

Se você já estudou estruturas de dados, provavelmente aprendeu que um Array oferece acesso por índice em O(1). Isso não significa acesso literalmente instantâneo: significa que o número de operações não cresce com o tamanho do array.

Mas como o Ruby encontra array[2]? Ele consulta o object_id? E esse ID revela o endereço real do objeto? No Ruby moderno, a resposta para as duas perguntas é não.

Como um Array encontra o elemento sem usar `object_id`

ruby
array = ["maçãs", "bananas", "pepinos"]

array[2] # => "pepinos"

Na implementação CRuby, um Array mantém seus elementos em posições consecutivas de uma área de armazenamento. Cada posição contém um VALUE, a representação de uma referência ou de um valor imediato na API C do Ruby.

Arrays pequenos podem guardar seus elementos dentro da própria estrutura RArray. Arrays maiores normalmente usam uma área separada apontada por RArray. Em ambos os casos, depois de validar e normalizar o índice, o acesso interno equivale a ptr[indice].

De forma conceitual, o cálculo é: endereço da posição = endereço inicial + (índice × tamanho de VALUE). Como não há varredura dos elementos anteriores, o custo é O(1). O código-fonte atual do CRuby mostra isso em rb_ary_entry_internal: depois dos testes de limite, ele retorna ptr[offset].

O que `object_id` realmente garante

A documentação oficial define object_id como um identificador inteiro único entre os objetos ativos do processo. O mesmo objeto devolve o mesmo ID durante sua vida, e dois objetos ativos diferentes não compartilham esse ID.

ruby
primeira = +"ruby"
segunda  = primeira
terceira = +"ruby"

primeira.object_id == segunda.object_id  # => true
primeira.object_id == terceira.object_id # => false
primeira.equal?(segunda)                 # => true
primeira.equal?(terceira)                # => false

Esse é um contrato de identidade, não de localização. A documentação não promete que os números sejam endereços, sequenciais, persistentes entre execuções ou adequados como identificadores de banco de dados.

De endereço de memória a identificador lógico

Até o Ruby 2.6

Para objetos alocados na heap, o CRuby codificava object_id a partir do ponteiro do objeto. Por isso, em versões antigas, certas transformações de bits pareciam revelar o endereço interno. Isso era detalhe de implementação, não uma garantia portátil da linguagem.

Ruby 2.7: GC Compaction muda o cenário

O Ruby 2.7 introduziu GC.compact. A compactação move objetos vivos entre slots da heap para reduzir fragmentação e melhorar o compartilhamento copy-on-write. Um ID derivado da localização não poderia mudar toda vez que o objeto fosse movido.

Nessa geração do CRuby, os objetos comuns passaram a receber IDs crescentes sob demanda, mantidos em estruturas internas que o Garbage Collector atualizava quando os objetos se moviam. Portanto, calcular object_id << 1 deixou de ser uma forma válida de recuperar o endereço.

Ruby 4.0: o ID passa a acompanhar os campos do objeto

No Ruby 4.0, lançado em dezembro de 2025, a implementação mudou novamente: o object_id de objetos comuns é armazenado junto aos campos do próprio objeto. A tabela inversa necessária para ObjectSpace._id2ref é construída apenas se esse recurso for usado. Isso reduz contenção e trabalho do GC.

Como a compactação preserva a identidade

ruby
objeto = Object.new
id_antes = objeto.object_id

GC.compact if GC.respond_to?(:compact)

id_depois = objeto.object_id
id_antes == id_depois # => true

GC.compact pode mover objetos na heap, mas todas as referências gerenciadas pelo Ruby são atualizadas. O object_id permanece estável porque identidade e localização são conceitos separados no Ruby moderno.

A exceção: valores imediatos

nil, true, false, Symbols, inteiros pequenos e alguns Floats são representados como valores imediatos. Em vez de apontarem para um objeto separado na heap, seus dados e marcações de tipo cabem no próprio VALUE.

ruby
42.object_id == 42.object_id       # => true
:ruby.object_id == :ruby.object_id # => true

"ruby".object_id == "ruby".object_id # => false

A última expressão cria duas strings mutáveis distintas. Já valores imediatos representam o próprio valor, e literais de string congelados também podem ser reutilizados pelo interpretador.

Resumo prático

  • Array#[] acessa uma posição do armazenamento interno por índice; não procura elementos por object_id.
  • Acesso por índice é O(1), mas ainda envolve validação do índice e acesso à memória.
  • object_id identifica logicamente um objeto ativo; ele não expõe sua localização atual.
  • Não derive endereços com object_id << 1 em Ruby moderno.
  • Use equal? para testar identidade de forma expressiva; use object_id principalmente para diagnóstico.
  • Não persista object_id nem o trate como UUID, token de segurança ou chave de negócio.

Fontes

EOF, Israel Santos

← voltar aos artigos