Este tutorial tenta ser uma introdução à linguagem Ruby, e assume que o leitor já possui algum conhecimento de programação em outra(s) linguagem(ens). Os assuntos são abordados aqui de forma mais ou menos superficial. É recomendável que o leitor consulte o manual e o livro Programming Ruby para tirar dúvidas e obter mais detalhes sobre a linguagem.
Este tutorial se baseia na linguagem entendida pelo interpretador Ruby v1.6.
Ruby tem um bom suporte multilíngüe. Podem ser usados nomes de classes, métodos, variáveis, etc. com acentuação, iniciando o interpretador com a opção -Ku (há outros modos, u é para UTF-8). Porém, para facilitar o uso com o Irb, os exemplos não usam identificadores com acentuação.
Este documento HTML foi testado, principalmente, no navegador WWW Lynx (o melhor), sendo perfeitamente legível. Outros navegadores não devem ter problemas para visualização.
É bastante recomendável instalar dois programas muito úteis (mesmo para quem já conhece a linguagem):
require "irb/completion"Linguagens de programação podem ser divididas em dois grupos: estáticas e dinâmicas.
Em geral, linguagens dinâmicas são mais lentas que estáticas, pois fornecem menos informações que possam ser usadas para otimização, e são, em geral, interpretadas.
Porém, principalmente graças ao enorme aumento de desempenho dos computadores, linguagens dinâmicas interpretadas estão se tornando cada vez mais populares, e tomando o lugar das estáticas em muitas áreas, principalmente nas que exigem rapidez e facilidade de desenvolvimento (Internet), já que nestes aspectos são bastante superiores.
Nestas linguagens, as variáveis e funções têm um tipo fixo, mas os objetos armazenados/apontados pelas variáveis não necessariamente têm tipo. Exemplos bem conhecidos deste grupo são Pascal, C, C++* e Java*. Qualquer valor/objeto "armazenado" na variável será tratado como sendo do tipo da variável. Caso o programador "force" (typecast) um tipo a uma variável, seu conteúdo será tratado como sendo deste tipo, não do seu tipo real/original.
Além de poderem ser mais otimizadas, linguagens estáticas também tem a vantagem de encontrar certos erros, relativos à incompatibilidade de tipos, em tempo de compilação, antes do programa rodar. Em uma linguagen dinâmica muitos destes erros só podem ser verificados durante a execução do programa.
Em linguagens dinâmicas as variáveis não necessariamente têm tipo, sendo apenas "etiquetas" que podem se referir a qualquer objeto/valor, e estes têm um tipo. Alguns exemplos deste grupo são Lisp, Python, Smalltalk e Ruby.
Isto significa que é possível fazer algo como:
a = 123; a = "abc"
Uma forma simplificada de visualizar a relação de classes, objetos e variáveis em uma linguagem dinâmica, como Ruby, pode ser a seguinte:
Isto permite uma grande flexibilidade. Em um exemplo simples, pode-se criar uma lista de itens, e nela armazenar-se quaisquer objetos de diferentes tipos.
Sendo Ruby uma linguagem dinâmica, variáveis não têm tipo, e são apenas referências a objetos*. Uma atribuição não copia o objeto. Isso quer dizer que ao executar b = a, estamos efetivamente fazendo com que a variável b se refira ao mesmo objeto que a. Por exemplo:
class Teste # Define classe Teste
def set(v) # Define método 'set' com argumento v
@v = v # Inicializa o atributo @v
end
def get
@v
end
end
a = Teste.new # a se refere a um novo objeto Teste
b = a # b se refere ao mesmo objeto que a
a.set("oi")
a.get # >> oi
b.get # >> oi
Pode ser feita a cópia de um objeto, usando-se o método clone (ou dup).
class Teste
def set(v)
@v = v
end
def get
@v
end
end
a = Teste.new # a se refere a um novo objeto Teste
b = a.clone # b se refere a uma cópia de a, ou seja, outro objeto Teste
a.set("oi")
a.get # >> oi
b.get # >> nil
Em Ruby, classes também são objetos (da classe Class), e como tais, respondem a métodos e têm todas as demais características de objetos normais.
false, true, nil, e números da classe Fixnum (entre 2**30-1 e -2**30) são valores imediatos, e não propriamente objetos. Portanto, não é possível definir métodos singleton para eles, por exemplo. Quando um valor inteiro ultrapassa os limites de Fixnum, é automaticamente convertido em um Bignum, que é um objeto normal.Não é necessário declarar variáveis da forma comum (como em C, Smalltalk, etc.), mas é necessário que a variável exista para que possa ser usada. Por exemplo:
puts a # >> NameError: undefined local variable ... a = nil puts a # >> nil
Ou seja, pode-se considerar que a atribuição faz a declaração automaticamente. Porém, variáveis globais têm valor nil mesmo antes de serem inicializadas.
puts $global # >> nil $global = "oi" puts $global # >> oi
Em Ruby as variáveis têm seu escopo determinado pelo nome. De certa forma, isso "oficializa" o hábito de nomear os diferentes tipos de variáveis usando diferentes regras de nomenclatura, encontrado em outras linguagens (por ex.: iniciar nomes de classes com letras maiúsculas, terminar variáveis de instância com sublinhado, etc.).
Variáveis globais são iniciadas por $:
$variavel_global = "abc"
Constantes são iniciadas por letra maiúscula (classes são constantes). Atribuição a uma constante já inicializada gera um aviso, mas seu valor é alterado:
CONST = 3.1415 # >> 3.1415 CONST = 6.02e+23 # >> warning: already initialized constant Constante puts CONST # >> 6.02e+23
Note que o objeto a que a constante se refere pode ser alterado. Quando for desejável manter um objeto imutável, usa-se o método freeze (de Object).
Atributos de objetos (variáveis de instância) são iniciados por @, e não são acessíveis externamente diretamente. Estas variáveis devem ser inicializadas e usadas nos métodos de instância. Quando definidas no corpo da classe, ficam no contexto da classe (são atributos do objeto da classe):
class A
@contexto = "classe"
def initialize
@contexto = "instância"
end
def contexto
@contexto
end
def A.contexto
@contexto
end
end
a = A.new
a.contexto # >> "instância"
A.contexto # >> "classe"
Atributos de classe são iniciados por @@ e não são acessíveis externamente diretamente. Estas variáveis são compartilhadas por todas as subclasses também. São como variáveis globais em relação à uma hierarquia de classes/módulos.
class Carro @@marcas = [ "Ford", "GM", "Fiat", "VW" ] end
E finalmente, variáveis locais, visíveis apenas no contexto do método/bloco/classe/módulo onde forem criadas, são iniciadas por letras minúsculas ou sublinhado:
variavel = 123 _variavel = "abc"
Algumas variáveis são especiais: true sempre se refere ao objeto booleano verdadeiro; false sempre se refere ao objeto booleano falso; nil sempre se refere ao objeto nulo; self sempre se refere ao objeto do método atual; __LINE__ é um Fixnum indicando a linha atual do código fonte; __FILE__ é uma String com o nome do arquivo fonte atual. Estes objetos são únicos, não podem ser clonados, e as variáveis não podem ser alteradas.
É importante dizer que, diferente de muitas linguagens, o valor 0 não é igual a false. Em expressões booleanas, os únicos valores falsos são false e nil, todos os outros são considerados verdadeiros.
A maioria dos comandos dá (retorna) um objeto como resultado, por isso comandos são também chamados de expressões. Não é necessário usar o comando return para retornar um valor. O objeto retornado pelo método será o resultado da última expressão avaliada.
def teste
if true
"aie"
else
"erro"
end
end
puts teste # >> aie
O comando return pode ser usado para fazer o "retorno imediato".
Comentários são iniciados pelo caractere '#' e se estendem até o fim da linha. Há também a documentação inserida em código, que inicia com a palavra =begin no inicio de uma linha, e termina com a palavra =end no inicio de uma linha posterior. Tudo que estiver entre elas será ignorado para fins de execução do programa. Além disso, texto entre estas palavras é "oficialmente" usado para gerar a documentação do código. Atualmente, o programa rdtool é usado para este fim, e utiliza uma formatação específica.
O Rdoc é outro programa para gerar documentação em vários formatos, tentando ser mais amigável ao programador. É muito elogiado e está ganhando a preferência dos desenvolvedores, sendo, portanto, mais recomendável seu uso. Além disso, há planos para fazer com que a documentação oficial das classes padrão seja feita com o Rdoc.
O fim da linha marca o fim de uma expressão, a não ser em alguns casos em que é óbvia (para o interpretador) a continuação da expressão, ou se o caractere '\' estiver no fim da linha. Também pode ser "forçado" o fim de uma expressão antes do fim da linha, com o uso de ';'. Assim é possível especificar várias expressões/comandos em uma única linha.
a = 1; b = 2; puts a+b # >> 3
Em geral, qualquer lugar onde seja esperado um objeto pode ter, ao invés de diretamente o objeto, uma expressão que resulte no objeto adequado.
Atribuição múltipla é feita quando o lado esquerdo de uma atribuição tem uma vírgula (',') após o primeiro elemento, seguida de 0 ou mais elementos também separados por ',', e é feita de acordo com as seguintes regras:
Array prefixado por '*', ele será "expandido";Array e expandido;nil;*', ele recebe um Array contendo seu valor correspondente seguido dos extras à direita (que, obviamente, não são descartados);A atribuição acontece efetivamente em paralelo (i.e. instantaneamente).
a = 1, 2, 3 # >> a=[1, 2, 3] a, = 1, 2, 3 # >> a=1 a, b, c = 1, 2 # >> a=1, b=2, c=nil a, b = b, a, 3 # >> a=2, b=1 a, *b = 1, 2, 3 # >> a=1, b=[2, 3]
A definição de uma classe se inicia com o uso da palavra class, e termina com a palavra end, da seguinte maneira:
class Classe end
Herança é expressa pelo símbolo de menor-que ('<') ao lado do nome da classe, seguido da super-classe. Por exemplo:
class Humano < Mamifero end
Na definição de uma classe pode-se ter quaisquer expressões válidas, ou seja, comandos, novas definições de classes, métodos, módulos, etc..
class Teste
class Teste
puts "A classe Teste::Teste está sendo definida."
def oi
puts "oi"
end
end
end
O operador de escopo, ::, é usado para acessar membros internos de uma classe, ou para acessar explicitamente uma constante externa a quaisquer classes/módulos, prefixando a constante.
a = Teste::Teste.new a::oi # >> oi a.oi # >> oi puts FALSE == ::FALSE # >> true
A inicialização das instâncias de uma classe é feita pelo método initialize (note o 'z'). Os métodos de nomes iniciados por attr (attr, attr_accessor, attr_reader, e attr_writer da classe Module) servem para declarar atributos e seus getters/setters automaticamente.
class Pessoa
attr "nome"
attr "idade"
def initialize(nome, idade)
@nome, @idade = nome, idade
end
end
p = Pessoa.new("Jeca", 33)
p.nome # >> Jeca
p.idade # >> 33
Definições de classes nunca estão fechadas. Ou seja, é possível fazer sua definição ao longo de várias partes do programa, da seguinte maneira:
class Teste
def oi
puts "oi"
end
end
class Teste # Continua a definição da classe
def tchau # Novo método
puts "tchau"
end
end
a = Teste.new
a.oi # >> oi
a.tchau # >> tchau
Porém, é possível chamar o método freeze de uma classe, e assim impedir sua alteração posterior.
class A end A.freeze class A def aie end end # >> TypeError: can't modify frozen class ...
Também é possível definir classes singleton, que são classes específicas a um objeto, usando class << objeto .... A forma para fazer isso é:
objeto = "abc"
class << objeto
def aie
"aie"
end
end
puts objeto.aie # >> aie
Outra forma, implícita, de criar uma classe singleton é definindo um método singleton, como é visto na seção "Métodos". Ambas as formas são equivalentes, porém pode ser mais conveniente usar uma ou outra, dependendo das circunstâncias.
É possível limitar a visibilidade de métodos e constantes, usando os métodos public (acesso público), protected (acesso apenas a objetos da classe e subclasses) e private (só podem ser chamados sem receptor explícito). Chamados sem argumentos, alteram a visibilidade padrão. Chamados com argumentos, alteram a visibilidade dos métodos e constantes especificados. Porém, devido a natureza dinâmica de Ruby, isto apenas dá uma certa formalidade à interface das classes.
Módulos são similares a classes, mas não podem ser instanciados, e não podem herdar nem serem herdados, mas podem ser incluídos em classes. Ao ser incluído, instâncias da classe que o inclui são também do "tipo" do módulo incluído. Por exemplo:
module M def faz_algo end end class C include M end a = C.new puts a.is_a?(M) # >> true
Desta forma são usados como mixins, substituindo (com vantagens e desvantagens) herança múltipla e interfaces (Java).
Módulos também tem um uso similar ao dos namespaces de C++, e packages de Java, que é de agrupar classes, valores, tipos, etc..
module Rede
module Servicos
TELNET = 23
SMTP = 25
FTP = 21
end
end
Definições de métodos são iniciadas com a palavra def, e terminam com a palavra end.
def oi puts "oi!" end
A declaração dos argumentos pode não ser feita entre parênteses, e estes podem ter expressões padrão (avaliadas, da esquerda para a direita, quando o método é chamado), especificados com o símbolo =.
def ola(p="pessoal", v=", "+p+"!\n") print "Olá ", p, v end ola # >> Olá pessoal, pessoal! ola "mortais" # >> Olá mortais, mortais! ola "enfermeira", " ...\n" # >> Olá enfermeira ...
Também não é necessário usar parênteses para chamar métodos, mas, a menos que a precedência das operações seja trivial, é recomendável. Deve ser notado que Ruby (ao menos por enquanto) tem algumas particularidades quanto a isso, e por vezes pode-se ter surpresas. O primeiro '(' encontrado após um nome é interpretado como sendo de uma chamada de método, portanto:
Math.sqrt (1-2).abs => (Math.sqrt(1-2)).abs p (1..10).to_a => (p(1..10)).to_a
Métodos são uma seqüência de expressões. O valor retornado pelo método é o valor da expressão avaliada por um comando return, ou o valor da última expressão avaliada.
def maior(a, b)
if a >= b
return a # >> retorna a imediatamente
end
b # >> retorna b
end
Caso o último argumento seja precedido por '*', este será um Array com os argumentos passados que excedam os declarados. Caso o último argumento seja precedido por '&', um bloco passado ao método é atribuído a este argumento como um objeto Proc. Pode-se ter ambos (*args e &bloco), sendo que *args seja o penúltimo, e &bloco o último.
Para resumir o comportamento das variáveis prefixadas por '*' em métodos e atribuições, pode-se pensar da seguinte maneira:
*', ele recebe um Array com os valores restantes;*', ele é "expandido" se for um Array; Pode-se definir métodos singleton, que são métodos específicos a um objeto, não a uma classe. Métodos de classe são implementados assim (lembre-se, classes são objetos). A especificação de tais métodos é feita da forma variável.método, ou usando a definição de classes singleton:
abc = "abc" def abc.def # Método singleton "def" end puts abc.def # >> def
Para "desdefinir" um método, usa-se undef. Para redefinir um método, basta defini-lo novamente. Caso seja necessário chamar o método original, deve-se usar o comando alias para dar um novo nome ao método antigo:
def aie puts "aie" end alias aie_antigo aie def aie puts "aie novo" aie_antigo end
Ao criar uma nova classe, pode-se chamar um método da classe base, que estiver sendo redefinido, com o comando super. Se nenhum argumento for explicitamente passado a super, os argumentos recebidos pelo método serão passados.
class Texto < String
def capitalize
puts "chamando capitalize ..."
super
end
end
a = Texto.new "aie"
a.capitalize # >> chamando capitalize ...
# >> "Aie"
A nomenclatura de métodos, em geral, segue algumas convenções:
true ou false têm seu nome terminado por '?';self, no método) são terminados por '!';=', e chamados automaticamente em uma expressão de atribuição, tendo como argumento o elemento à direita da atribuição. Tais métodos são definidos automaticamente ao usar os métodos attr, attr_accessor, e attr_writer, de Module, para declarar atributos;
a = "oi!" b = "oi!" a.empty? # >> false a.capitalize! # >> "Oi!" a # >> "Oi!" b.capitalize # >> "Oi!" b # >> "oi!" def a.teste=(val) @teste = val end def a.teste @teste end a.teste = "aie" puts a.teste # >> "aie"
Ruby tem os seguintes operadores (em ordem decrescente de precedência):
| Operador | Função | É método? (redefinível) |
:: | Escopo | Não |
[] | Referência (Array) | Sim |
** | Expoenciação | Sim |
-, +, !, ~ | -, +, Neg., Compl. (unários) | Sim |
*, /, % | Mult., Div., Mód. | Sim |
+, - | Adic., Subtr. | Sim |
<<, >> | Deslocamento | Sim |
& | "E" binário | Sim |
|, ^ | "Ou", "Ou exclusivo" | Sim |
>, >=, <, <= | Comparação | Sim |
<=>, ==, ===, !=, ~=, !~ | Igualdade | Sim* |
&& | "E" lógico | Não |
|| | "Ou" lógico | Não |
.., ... | "Faixas" incl. e excl. | Não |
?: | if-then-else | Não |
=, +=, -=, etc. | Atribuição | Não* |
defined? | Testa definição de um símbolo | Não |
not | "Não" lógico | Não |
and, or | "E", "Ou" lógicos | Não |
if, unless, while, until | Modificadores | Não |
begin, end | Expressão em bloco | Não |
Os operadores redefiníveis são, na verdade, métodos em uma forma especial, para maior comodidade. Por isso podem ser redefinidos.
Note que, por serem redefiníveis, os significados dos operadores dependem das suas implementações. Por exemplo, a classe Array implementa o operador & como intersecção de conjuntos.
a = ["a",2,3,4] b = ["a",2,5,6] a & b # >> ["a", 2]
Apesar de não serem operadores nesses casos, os caracteres ':' e '?' têm usos especiais, para gerar símbolos e o código numérico de um caractere, quando prefixam uma seqüência de caracteres e um único caractere, respectivamente.
:aie.class # >> Symbol ?a # >> 97
Símbolos são a representação interna dos nomes. Duas ocorrências de um símbolo sempre se referem ao mesmo objeto da classe Symbol.
a = :aie b = :aie a == b # >> true
Símbolos também são valores imediatos, e estão sujeitos às mesmas restrições. Um uso para símbolos é como chaves de um Hash, sendo que são mais eficientes que strings. Pode-se obter o símbolo correspondente a uma String usando o método intern, e obter a String correspondente a um símbolo usando o método to_s de Symbol.
Há vários métodos, como os attr, que aceitam strings ou símbolos como argumentos.
!= e !~) são redefinidos automaticamente a partir dos de igualdade correspondentes, e não podem ser redefinidos "manualmente".+=, -=, etc.) são redefinidos automaticamente a partir dos seus correspondentes normais (+, -, etc.), e do operador de atribuição normal (=), e não podem ser redefinidos "manualmente". Iteradores são métodos que executam um bloco de código que lhes seja passado. Blocos são delimitados por { ... }, ou do ... end ({} tem maior precedência), e podem receber argumentos, declarados entre | ... |.
"abcdef".each_byte { |c| # O método each_byte vai executar o bloco para cada byte da string,
print c, " " # lhe passando cada byte pelo argumento c
}
Quando definindo iteradores, o bloco recebido é executado pelo comando yield, e os argumentos passados a yield serão atribuídos aos argumentos do bloco por atribuição múltipla.
class ListaEncadeada
...
def each
... # Passa por todos os elementos
yield(elemento) # executando o bloco recebido, com o elemento atual como argumento
end
end
lista = ListaEncadeada.new
...
lista.each { |e| print e }
Iteradores são muito úteis em substituição a laços, em muitos casos. Note que blocos não são úteis apenas à iteradores. Eles podem ser passados a qualquer método. Além disso, blocos podem ser convertidos em objetos Proc, e dessa forma atribuídos a variáveis, passados como argumentos normais, etc..
def teste(um_bloco)
um_bloco.call
end
bloco = proc {
puts "oi!"
}
teste(bloco) # >> oi!
É importante destacar algo que pode ser fonte de confusão. O contexto de variáveis do bloco é o mesmo de onde ele foi criado. O bloco tem acesso às variáveis locais do bloco onde foi criado, por exemplo. Mas o bloco não introduz novas variáveis no contexto onde foi criado, apenas no seu próprio. Ou seja, variáveis já existentes podem ser usadas em um bloco (inclusive variáveis de instância, de classe, e globais), e variáveis de um bloco são locais ao bloco, caso não existem anteriormente.
l = "z"
m = "1"
["a", "b", "c"].each { |l|
print l # >> abc
m = "2"
n = l
}
puts l # >> c
puts m # >> 2
puts n # >> NameError: undefined local variable or method `n' ...
Ruby tem as estruturas de controle comuns à maioria das linguagens mais conhecidas. São elas:
if .. [then] # if tradicional .. [elsif .. [then] ..] [else ..] end unless .. [then] # negação do if (a menos que ...) .. [else ..] end case .. # execução por casos (comparação com o operador ===) [when .. [,..] [,..] [then] ..] [else ..] end while .. [do] # while tradicional (faça enquanto ...) .. end until .. [do] # negação do while (até que ...) .. end for i in .. [do] # iteração por uma lista de objetos .. end
Além disso, if, unless, while e until podem ser usadas como modificadores, da seguinte forma:
puts "oi!" if bem_vindo expulsa_pessoas until recinto.vazio?
Estas estruturas de controle retornam o valor da última expressão avaliada por elas.
Há também BEGIN e END, que recebem blocos, que serão executados respectivamente antes e depois da execução do restante do programa. Múltiplos blocos podem ser definidos, mas apenas no nível mais externo do arquivo (fora que quaisquer classes ou módulos). Blocos BEGIN são executados na ordem de definição. Blocos END são executados na ordem inversa de definição.
# Obs.: isto não funciona no Irb
puts "aie"
END { puts "finalizando" }
BEGIN { puts "inicializando" }
Em laços e iteradores, break serve para sair do laço/bloco mais interno, next começa imediatamente a próxima iteração, redo reinicia a iteração atual, e retry reinicia a iteração reavaliando a condição (laços) ou refazendo a atribuição de argumentos (iteradores).
Exceções são uma forma eficiente e "limpa" de tratar erros e outras situações previstas e imprevistas em um programa.
Ruby (assim como outras linguagens OO), usa objetos para representar exceções. Tais objetos são da classe Exception, e têm uma String com uma mensagem de erro, e informações sobre o estado da pilha (stack traceback).
Para causar uma exceção é usado o método raise (ou fail), da classe Kernel. raise pode ter as seguintes formas:
raise raise mensagem_ou_exceção raise tipo_de_erro, mensagem raise tipo_de_erro, mensagem, traceback
Para responder a uma exceção, é usado o comando rescue em uma expressão begin ou método. O comando else pode ser usado para executar um bloco quando rescue não for acionado. ensure pode ser usado para definir um bloco que será executado sempre.
def espera_regexp(re)
linha = readline while linha !~ re
rescue EOFError
puts "Fim do arquivo."
linha = nil
else
linha
ensure
if linha.class != String || linha != nil
linha = nil
end
end
O comando retry também pode ser usado no tratamento de erros. Ele faz com que o bloco atual seja executado novamente, desde o início. Mas deve-se ter cuidado para não gerar laços infinitos.
Os métodos throw e catch são parecidos com raise e rescue, porém, não lidam com exceções, mas sim com símbolos ou strings (labels). catch executa o bloco que lhe é passado, mas pára a execução caso um throw seja executado com o símbolo correspondente. O objeto retornado é o da última expressão do bloco, caso ele termine normalmente, ou o que for passado à throw, ou nil.
resultado = catch(:aie) {
puts "Executando catch ..."
throw(:aie, "ops")
puts "Não vai chegar aqui."
"Fim."
}
puts resultado # >> ops
Isto é útil para sair de um laço de execução muito "profundo", por exemplo.
Arrays (vetores) podem ser usados para muitas coisas. Ao contrário de arrays "comuns", de tamanho fixo, como são encontrados em muitas linguagens, arrays em Ruby têm tamanho dinâmico. E além de arrays, também podem ser usados, por exemplo, em substituição a listas e pilhas, em muitos casos. Podem ser criados com uma expressão de array, que é uma lista de expressões separadas por vírgulas, entre os símbolos [], da seguinte forma:
a = [ 1, 2, 1+2 ] # >> [1, 2, 3]
Ou também explicitamente, usando os métodos da classe Array:
a = Array.new a << 1 << 2 << 1+2 # >> [1, 2, 3] b = Array[1, 2, 1+2] # usando o método [] da classe Array
Por Ruby ser uma linguagem dinâmica, arrays podem conter qualquer objeto.
Hashes (tabelas associativas) são muito parecidos com arrays, mas têm objetos como índice. Um Hash apenas com índices numéricos é praticamente equivalente a um Array. Podem ser criados com uma "expressão de hash", que é uma lista de pares índice => valor, separados por vírgulas, entre os símbolos {}, da seguinte forma:
h = { "a"=>"letra a", "1+1"=>1+1 } # >> {"1+1"=>2, "a"=>"letra a"}
Ou também explicitamente, usando os métodos da classe Hash:
a = Hash.new b = Hash["a",1 , "b",2 , "c",1+2] # usando o método [] da classe Hash
Seguindo a "tradição" de várias linguagens script de UNIX, strings podem ser expressas por aspas duplas e simples, com propósitos diferentes.
As expressas com aspas duplas ('"') podem ter várias substituições de "códigos de escape", como '\t' para tabulação horizontal, '\n' para nova-linha, etc. (consulte o manual). Além dessas substituições simples, podem ter substituições de qualquer expressão, entre os caracteres #{}.
msg = "oi!"
puts "msg: #{msg}" # >> msg: oi!
puts "#{ def oi; 'tchau!'; end; oi }" # >> tchau!
puts %Q|Esta string pode ter " e '.| # >> Esta string pode ter " e '.
puts %q[Outra string.] # >> Outra string.
As strings expressas com aspas simples (''') podem ter apenas as substituições de escape '\'' (que permite a existência de ''' sem indicar o término da string), e '\\' (que permite a existência de uma '\' no fim da string, sem indicar a substituição '\''). Quaisquer outros caracteres são tratados de forma literal.
puts "1+1=#{1+1}" # >> 1+1=2
puts '1+1=#{1+1}' # >> 1+1=#{1+1}
Outras formas de expressar strings são usando a sintaxe %q/string/, que equivale ao uso de aspas simples, e %Q/string/, que equivale ao uso de aspas duplas. O caractere delimitador '/' pode ser substituído por qualquer outro não alfanumérico (por ex.: %Q(), %q||). Estas formas são úteis nos casos de strings contendo muitas aspas simples ou duplas, de acordo com o tipo de string utilizada. Ainda, é possível especificar strings ao longo de várias linhas (here documents), e com diferentes funções, mas isso não será explicado aqui. Consulte o manual, na parte de sintaxe, na seção "line-oriented string literals".
Strings são objetos da classe String. Veja as suas características e métodos disponíveis no manual e no livro Programming Ruby.
Expressões regulares são muito úteis no processamento de texto. Podem ser usadas, por exemplo, para separar campos, fazer buscas e substituições. As expressões regulares de Ruby são similares às de Perl, que por sua vez são similares às do comando egrep (UNIX/GNU/Linux/etc.). Caso você desconheça este comando, ele é usado para fazer buscas em arquivos, sugiro que dê uma olhada no seu manual ("man 1 egrep"), e/ou no manual da biblioteca GNU regex ("man 7 regex"). Porém, Ruby tem extensões a estas regexps. Consulte o livro Programming Ruby para uma referência completa.
Elas são expressas entre dois caracteres '/', ou, de forma semelhante às strings, %r//.
r = /oi!/ # >> busca a string oi! r = /^print/ # >> busca a string print em começo de linha
Como pode ser notado, não há qualquer referência a gerenciamento (alocação/liberação) de memória neste tutorial. Isto é porque Ruby utiliza um mecanismo conhecido como coletor de lixo para gerenciar a memória. A primeira linguagem a usar coletor de lixo foi LISP, por volta de 1960. O coletor de lixo se encarrega de encontrar áreas de memória (ocupadas por objetos) não mais em uso, e as libera. Isto, além de liberar o programador desse aborrecimento, evita erros e pode melhorar o desempenho, pois otimizar a alocação e liberação de memória não é uma tarefa fácil para pessoas, principalmente quando muitos objetos estão envolvidos, e os algoritmos de coleta de lixo já são bastante eficientes.
Muitas outras linguagens usam este mecanismo, principalmente linguagens interpretadas. Alguns exemplos são Smalltalk, LISP, Perl, Python, Java, TCL, Lua.
Porém, o coletor de lixo pode ter algumas desvantagens. Uma delas é que o programador tem menor controle sobre a gerência de memória, nos casos em que isto se faz necessário. Outra, referente à técnica de coleta conhecida como "contagem de referências" (reference counting), que é usada nos interpretadores de Perl e Python, por exemplo, é que pode haver "vazamento" de memória (memory leaking), devido a referências circulares. Outro problema, referente à técnica conhecida como "marca e varre" (mark and sweep), que é usada em Ruby e LISP, por exemplo, é que é difícil saber o momento exato e a ordem em que os objetos são destruídos, e isso impede a definição de destrutores (destructors) em certos casos. Mas, em geral, a coleta de lixo é muito útil e facilita muito a vida do programador, sem incorrer em custos significativos na grande maioria dos casos.
Existem bibliotecas que fornecem mecanismos de coleta de lixo para linguagens sem suporte nativo, como C e C++.
Ruby tem um modelo de segurança bastante simples, similar ao de Perl e JavaScript, chamado data tainting ("corrupção de dados"). Qualquer dado vindo de "ambiente possivelmente hostil" (dados fornecidos pelo usuário, principalmente), é marcado como "corrupto" (tainted), e esse dado não pode mais ser usado para certas operações, como por exemplo, executar um comando externo, inadvertidamente. A variável global $SAFE indica o nível de segurança. A seguinte tabela indica os níveis existentes:
A variável $SAFE só pode ser incrementada, nunca decrementada.
Este texto foi escrito por Gustavo De Nardin, em períodos esparsos de 2001.
A última modificação foi em Dezembro de 2001.
Comentários, críticas, sugestões, etc. > e/2.
© Gustavo De Nardin, 2001.
Pode ser usado e distribuído livremente, sem limitações, em qualquer formato. Pode ser modificado e redistribuído, desde que sejam indicadas as modificações e seus autores, e não sejam alteradas estas condições, nem as indicações de modificações e autores que aqui já constarem, nem esta nota de copyright.
Caso seja feita alguma modificação, favor enviar uma cópia ou indicação de como obter uma (URL, por exemplo).