10.02.2026
O que há de novo na 4.0?
Ruby 4.0.0 foi lançado no ano passado (seguido de uma pequena atualização recentemente), e temos algumas mudanças para comentar. Vamos focar nos novos recursos que parecem promissores e mostrar alguns trechos de código. Mas antes, há algumas perguntas que as pessoas fazem quando sai uma nova versão da linguagem que usam em aplicações de produção, e podemos fornecer pelo menos algumas das respostas.
Há novos recursos menores que não vão dar muito o que pensar, como os operadores lógicos binários podendo continuar no início da próxima linha:
if cond1
&& cond2
|| cond3
...
Essas coisas são sempre bem-vindas, mas não vamos listá-las aqui. Em vez disso, vamos focar nas novidades maiores que apareceram nas notas de lançamento.
Nova versão da Ruby! Será que devo atualizar?
Não. Quer dizer, a menos que você esteja interessado em testar os novos recursos, e se for esse o caso, você já tem as respostas para suas perguntas (pelo menos aquelas que podemos esclarecer).
Atualizar a versão do Ruby da sua aplicação exige planejamento, testes suficientes para garantir que tudo esteja funcionando antes da atualização, e ainda mais testes porque, depois da atualização, podem quebrar coisas que você não previu (ou nem podia prever) com os testes anteriores. Mesmo que as mudanças provavelmente não quebrem seus programas (não há alterações incompatíveis de sintaxe; apenas adições que a tornam mais legível), é melhor esperar. Planeje, teste, faça garantia de qualidade, e só então atualize, para depois poder repetir todo o processo novamente.
Mesmo que seja divertido brincar com os novos recursos, nós não recomendamos adicioná-los a aplicações existentes ainda:
- Ruby Box, para separar contextos, é desabilitado por padrão
- ZJIT, um JIT alternativo e sucessor do YJIT, é dito ser mais lento que o YJIT (por enquanto)
- Ractor (um auxiliar para execução paralela) recebeu algumas melhorias, mas inda é um recurso experimental
Então pegue leve, se familiarize com os recursos novos e talvez escolha um ou dois que sejam úteis para seu caso específico. Mas deixe os testes prontos antes de qualquer coisa.
A caixa de rubis
Ruby Box é um novo recurso (que foi chamado de namespaces por um tempo) feito para prover um contexto isolado num processo Ruby. O anúncio trouxe alguns exemplos de como espera-se que ele seja usado, então daremos uma mostra:
Em `article_box.rb`:
class Array
def monkey_patch
'🐒'
end
end
def box_method
'✨'
end
$GLOBAL_VAR = '🌍'
TOP_LEVEL_VAR = '🐉'
Em `main.rb`:
box = Ruby::Box.new()
box.require_relative('article_box')
monkey = box.eval('[].monkey_patch')
globe = box.eval('$GLOBAL_VAR')
top = box.eval('TOP_LEVEL_VAR')
box_method_return = box.eval('box_method')
p monkey # Mostra "🐒"
p globe # Mostra "🌍"
p top # Mostra "🐉"
p box::TOP_LEVEL_VAR # Mostra "🐉"
p box_method_return # Mostra "✨"
p $GLOBAL_VAR # nil
p TOP_LEVEL_VAR # NameError
p [].monkey_patch # NoMethodError
p box::Array.new.monkey_patch # NoMethodError
p box::box_method # NoMethodError
Como pode ver, o "monkey patch" fica na box, e você pode acessar as diferentes variáveis, módulos, classes, etc. que foram definidas usando o objeto box, ou avaliar strings de código Ruby e usar seja lá o que retornarem (isso é, se você gostar de viver perigosamente, embora sejam úteis para mostrar esses exemplos curtos sem depender de vários arquivos e "require," então não vamos julgar).
Só um lembrete de que o contexto atual não é preservado ao criar uma box nova.
require 'json'
box = Ruby::Box.new()
box.eval("{a: 5}.to_json") # NoMethodError
p({a: 5}.to_json)
Você teria que `require 'json'` dentro da box também (se você precisa dessa funcionalidade, você ainda pode usar fork).
Você também não pode chamar um método top-level da box sem usar eval:
box = Ruby::Box.new()
box.eval('def test_method; "💯"; end')
hundred = box.eval('test_method')
p hundred # Will print "💯"
p box::test_method # NoMethodError
Então, será que vai ser super útil na sua aplicação? Considere que é um recurso experimental, requer modificar uma variável de ambiente para funcionar (RUBY_BOX=1), e que vai te mostrar um belo aviso:
ruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
Ainda estamos esperando encontrar alguns usos interessantes para esse recurso. A recomendação é brincar com ele, talvez fazer um protótipo divertido se você tiver uma ideia bacana e ficar atento aos problemas conhecidos.
Ractors têm esse nome porque são Ruby Actors
Ractor é um recurso experimental adicionado junto ao lançamento do Ruby 3.0 para "fornecer funcionalidade de execução paralela sem preocupações de segurança de threads." Continua sendo um recurso experimental no Ruby 4.0, mas agora recebeu uma adição interessante.
Antes, ao trabalhar com múltiplos Ractors em um ambiente onde era necessário receber algum resultado do Ractor, não havia como retornar esse resultado a menos que você passasse algo que pudesse recebê-lo. Por exemplo, outro Ractor podia receber o resultado, um pouco como channels em Go:
def calc(n)
n.times.map { n * n }.sum
end
server = Ractor.new do
while true
n, sender = Ractor.receive
result = calc n
sender << result
end
end
server << [10, Ractor.current]
p Ractor.receive # will print 1000
Pode-se ver que existem alguns problemas com isso. Usar o Ractor atual não é suficiente se houverem mais servidores, e ter que simular canais através da criação de Ractors parece ...um desperdício. Há um ótimo post explicando a necessidade dessa nova implementação.
Com os novos ports, podemos fazer o mesmo exemplo para dois (ou mais) servidores com facilidade:
def calc(n)
n.times.map { n * n }.sum
end
port1 = Ractor::Port.new
port2 = Ractor::Port.new
server1 = Ractor.new(port1) do |port|
while true
n = Ractor.receive
value = calc(n)
port << value
end
end
server2 = Ractor.new(port2) do |port|
while true
n = Ractor.receive
value = calc(n)
port << value
end
end
server1 << 10
server2 << 20
p port1.receive # will print 1000
p port2.receive # will print 8000
No fim das contas, o importante é lembrar: só o Ractor que criou um port (por exemplo, o Ractor principal no nosso caso, que é usado por padrão por todo programa Ruby) pode chamar o método "receive" para aquele port. Então não podemos receber de outro port que foi passado como parâmetro; só podemos enviar para ele. Ainda assim, funciona porque eles tem seus próprios ports padrões e podem receber por eles (com "Ractor.receive").
Devo usar isso nos meus programas? Se você já está usando uma das funcionalidades da Ruby para programação concorrente, talvez queira se preparar para migrá-los para Ractors caso ache que isso vai melhorar seu código. Se não, então, novamente, brinque com esse recurso. Ainda é experimental (o que significa que aparece um aviso ao usar, mas pelo menos dessa vez ele não requer mudar uma variável de ambiente), mas estão planejando remover o status de "experimental" ainda este ano.
Os JITs e a Performance
Boa performance é uma das coisas mais importantes que as pessoas esperam de toda linguagem de programação já feita, mas dessa vez é relacionado a um novo JIT para Ruby chamado ZJIT. A não, ZJIT ainda não está pronto, e nem é ainda um substituto para o velho YJIT, mas está sendo apresentado como a "próxima geração" do YJIT. Podemos facilmente ativá-lo usando "--zjit" ao chamar o interpretador, igual fazíamos com YJIT.
Temos outras melhorias de performance? É difícil dizer, já que é sempre necessário testar na sua própria aplicação, com o tipo de código que você está trabalhando, e com as pequenas otimizações que você fez e que assumiam em algum momento que algumas coisas eram melhores que outras em velocidade e memória.
Mas pelo menos podemos testar em algo divertido. O site Benchmarks Game sempre tem alguns códigos legais para esse tipo de coisa, e podemos até comparar com a versão anterior da Ruby. Decidimos fazer alguns testes rápidos: spectral-norm e binary trees. Testamos num notebook simples que temos à mão (Intel Core i5-8265U, 1.60GHz), executando cada programa cinco vezes e tirando média do tempo gasto. Além disso, esses dois testes não precisam de muito I/O (não precisam ler arquivos), então são perfeitos como programas de teste para melhorias pequenas.
|
Versão Ruby |
Ferramenta |
Programa |
Tempo (segundos) |
|---|---|---|---|
|
3.4.8 |
YJIT |
73.413 |
|
|
4.0.1 |
YJIT |
82.761 |
|
|
4.0.1 |
ZJIT |
95.848 |
|
|
3.4.8 |
YJIT |
29.035 |
|
|
4.0.1 |
YJIT |
24.987 |
|
|
4.0.1 |
ZJIT |
38.491 |
Lembre-se, isso significa muito pouco para seus programas, mas significa muito para a diversão de conferir melhorias de performance. E pelos resultados, o que podemos dizer? Sabemos que melhorar a performance de pacotes e linguagens de programação não progride linearmente, e que não é tão simples quanto "Versão nova, agora as coisas serão mais rápidas!," e sabemos que podem haver novos recursos, verificações, ou seja lá mais o quê adicionado à linguagem.
Um teste ficou mais rápido, e o outro ficou mais lento, mesmo que a única diferença fosse a versão da Ruby. É um ótimo jeito de mostrar que sua experiência pode variar e você não deve usar as mudanças de desempenho em programas teste como referência para a decisão de atualizar sua linguagem atual. Já esperávamos que ZJIT fosse mais lento que o YJIT (nos avisaram nas notas de lançamento), e ainda temos um bom caminho a andar antes que se torne tão bom quanto o que nós já temos.
Pelo menos isso serve como um bom lembrete para sempre ter medições de referência para as partes do código que você já sabe que são lentas.
Conclusão
Ainda temos muito a esperar, mas já ganhamos algumas coisas interessantes para brincar. As mudanças para programas existentes foram pequenas, mas as promessas são bem grandes, e assim que Ractor sair da fase experimental, celebraremos um grande passo para a linguagem. E a previsão é que lance ainda este ano.
Ruby Box deve levar mais tempo, mas ao menos temos o suficiente para poder testar. O mesmo serve para o ZJIT. Estamos vivendo a fase de testes, mas em alguns anos colheremos essas deliciosas melhorias de desempenho. Vamos até precisar delas para nos manter à altura das versões anteriores, pelo visto.