Você sabe explicar a diferença entre as duas linhas abaixo?
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
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.
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:
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:
>> $".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:
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:
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.
Muito legal a abordagem desse assunto, embora em algumas outras linguagens o comportamento para ambos sejam semelhantes aos do ruby, ainda sim existem peculiaridades no método load que são muito interessantes. Belo artigo parabéns.
Æ!!
Legal o artigo Carlos!
É interessante ver as entranhas das coisas e entender exatamente o que elas fazem. Continue postando suas experiencias nisso que com certeza serão muito válidas para todos.
Há braços
Muito bom este artigo, aprendi bastante.
Olá Brando, interessante também é o require_relative presente no Ruby 1.9 em diante, que não procura por bibliotecas e/ou arquivos no $LOAD_PATH mas sim no diretório atual do arquivo que os carrega.
rodrigo3n,
Interessante, não conhecia este.