Carlos Brando

Nome do Jogo

Entendendo os métodos load e require por dentro

Você sabe explicar a diferença entre as duas linhas abaixo?

1
2
3
require "my_library"

load "my_library.rb"

Se você já trabalhou ao menos um pouco com Ruby, então sabe que load é útil quando se deseja executar o código de um arquivo enquanto require é utilizado para importar uma biblioteca para dentro do código do seu programa.

Esta definição está correta, mas eu gostaria de me aprofundar um pouco mais no assunto…

A Base

Para entender como esses métodos realmente funcionam por dentro, precisamos primeiro saber que ambos possuem a mesma base. No Ruby 1.8.7 esta base é o método rb_load (internamente escrito em C e encontrado no arquivo eval.c). A função do rb_load é determinar se o arquivo especificado na chamada dos métodos require e load se encontra no diretório atual do aplicativo ou em um dos diretórios listados no $LOAD_PATH ($:) e então carregar este arquivo seguindo a regra própria de cada um dos métodos.

O método require por dentro

1
require "my_library"

Quanto rb_load é executado através do método require, fazendo com que ele carregue uma biblioteca no seu código, o nome do arquivo especificado é adicionado a uma lista. Esta lista pode ser vista através da variável global $LOADED_FEATURES ($").

Você pode verificar quais arquivos já foram carregados em seu código através do método require analisando o conteúdo desta variável.

[carlosbrando:~]$ irb
>> puts $LOADED_FEATURES
enumerator.so
e2mmap.rb
irb/init.rb
...

Ao executar o método require, o Ruby primeiro verificará se o nome do arquivo que você especificou se encontra em $LOADED_FEATURES. Se ele não encontrar o nome na lista, então ele tentará carregar o arquivo e se for bem sucedido retornará true. Caso o nome já se encontre na lista de recursos carregados, então o método simplesmente retornará false e não carregará o arquivo novamente.

O segundo passo é determinar em que diretório o arquivo se encontra. Para isto o Ruby consultará uma outra variável global chamada $LOAD_PATH. Esta variável armazena todos os diretórios onde rb_load deve procurar por bibliotecas. Se o arquivo não for encontrado no diretório raiz do seu aplicativo e nem na lista de diretórios do $LOAD_PATH, então o método rb_load disparará um erro do tipo LoadError.

Caso você não informe uma extensão para o nome do arquivo, como no exemplo no inicio deste tópico, o método require procurará por arquivos com as seguintes extensões: .rb, .o, .so, .dylib, .dll, exatamente nesta ordem. Por último o Ruby procurará pelo nome do arquivo sem nenhuma extensão. Se o arquivo possuir a extensão .rb ele será carregado como um código Ruby puro. As demais extensões serão carregadas como extensões Ruby de acordo com o sistema operacional na qual você estiver executando o seu código.

Atenção: Se você informar a extensão do arquivo na chamada do método require (i.e. require 'my_lib.rb') e o Ruby não conseguir encontrar o arquivo da forma como foi especificado, ele ignorará a extensão que você definiu e procurará por arquivos com o mesmo nome seguindo a regra mencionada no parágrafo anterior.

O método também permite que você seja mais especifico, informando o diretório exato onde o arquivo se encontra.

1
require '../my_library.rb'

No exemplo acima o Ruby não tentará localizar o recurso em outros diretórios, caso não o encontre no local que você especificou. Porém, é importante estar atento a um problema comum ao utilizar este recurso. Veja o código abaixo:

1
2
require 'my_library'
require './my_library.rb'

Neste exemplo, nas duas linhas de código eu estou carregando o mesmo recurso. Na primeira linha eu informo somente o nome do arquivo e na segunda eu informo também o diretório. O método require não armazena o caminho absoluto do recurso em $LOADED_FEATURES, ele armazena a string exata informada na chamada do método. Neste caso o arquivo my_library.rb será carregado duas vezes. Analisando a variável $LOADED_FEATURES encontraremos o seguinte:

1
2
>> $".grep /my_library/
=> ["my_library.rb", "./my_library.rb"]

O método load por dentro

O método load funciona de uma forma muito parecida com o método require. A principal diferença é que ele não mantém uma lista de recursos, fazendo com que um arquivo seja carregado tantas vezes quanto for solicitado.

Outra importante diferença é que a extensão do arquivo é obrigatória, embora ela não seja restrita apenas a .rb e aos outros formatos que vimos acima. Qualquer outra extensão é aceita, embora o conteúdo do arquivo seja sempre tratado como código Ruby puro.

Você também pode especificar o caminho exato onde o arquivo se encontra. Desta vez, sem nenhum efeito colateral. Porém, se você informar somente o nome do arquivo, o Ruby tentará encontrá-lo primeiro no seu diretório atual e depois na lista de diretórios do $LOAD_PATH.

O método load tem algumas opções extras. Muito provavelmente o arquivo que você estará carregando definirá algumas variáveis e classes. Variáveis locais em nenhuma circunstancia serão propagadas para o seu ambiente, porém o mesmo não acontece com constantes. O resultado é que ao carregar um recurso em seu projeto você pode ter um conflito se já houver uma constante com o mesmo nome no seu código. O mesmo vale para nomes de classes, que como já aprendemos também são constantes.

Você pode obrigar o método load a manter suas constantes para si mesmo, passando um segundo parametro na chamada do método:

1
load 'my_library.rb', true

Quando você carrega um recurso desta maneira, o Ruby criará um módulo anônimo e usará este módulo como um namespace, evitando conflitos entre o código do arquivo e o seu código. Ao terminar a execução este módulo é destruído. No exemplo acima, true é um atalho. Se desejar, você pode ser mais especifico fazendo também desta maneira:

1
load "my_library.rb", :wrap => true

O efeito é o mesmo.

Finalizando

O método require não tem opções extras. Se você está importando uma biblioteca então faz sentido que constantes e classes permaneçam em seu código. Por outro lado, se você deseja apenas executar o código que está em um arquivo, provavelmente você não vai querer poluir o seu programa com recursos desnecessários.

Os dois métodos são muito parecidos, embora eles tenham sido construídos para finalidades diferentes. Entender como cada um funciona por dentro pode ajudá-lo a utilizar o recurso certo na hora certa.

Comments