No início de 2013 li o excelente tutorial Rust for Rubyists do Steve Klabnik. Na época gostei bastante da linguagem mas acabei não procurando mais informações. Desde então, ela vem evoluindo bastante e está se tornando bastante popular. Se você ainda não conhece, vou falar rapidamente de algumas coisas que me chamaram a atenção.
Quem deve usar Rust
Antes de mais nada é importante deixar claro qual o objetivo da linguagem e quem deve usá-la.A linguagem Rust está sendo desenvolvida pela Mozilla para ser usada no lugar do C++. Os principais objetivos dela são segurança, concorrência e praticidade. A Mozilla usará a linguagem especialmente no Servo, um novo browser que estão desenvolvendo.
Pelo fato dela ser usada onde normalmente o C++ é adotado, ela precisa fornecer controles em baixo nível, especialmente no gerenciamento de memória, mas ela faz isso sem comprometer a estabilidade e segurança, ao contrário do C++.
Ainda não está totalmente claro em que áreas a linguagem será adotada, mas eu imagino que as principais serão:
- Quem atualmente desenvolve em C ou C++ - para essas pessoas o Rust permitirá o mesmo controle que o C permite, porém, com muito mais segurança. Para essas pessoas a adoção do Rust será uma ótima escolha;
- Aplicações web que fornecem APIs - no momento a linguagem Go está sendo bastante utilizada nesses casos. Eu não sou grande fã de Go (também não usei muito para poder opinar), imagino que o Rust acabará sendo adotado nesses casos também;
- Otimizações - em Ruby é comum desenvolver em C partes do código que precisam de alta performance. Rust poderá vir a tomar esse lugar, por ser bem mais legível (coisa que rubistas valorizam bastante) e ser mais segura.
Variáveis
Ao definir as variáveis não é necessário especificar o tipo, na maioria dos casos o compilador consegue inferir o tipo a ser usado.Por padrão as variáveis são imutáveis (por questões de performance e concorrência). Para que a variável seja mutável é necessário definí-la usando o "mut".
let hi = "hi"; let mut count = 0; while count < 10 { println!("count is {}", count); count += 1; }
Gerenciamento da memória
Não vou entrar em detalhes do gerenciamento da memória feita pelo Rust, mas o importante é que ele detecta durante a compilação possíveis problemas.No exemplo abaixo, em C++, repare que é retornado um ponteiro para uma área de memória que logo ficará inválida. Esse erro é bastante comum e não é algo que a linguagem C++ previne.
{ // A memória será liberada no final da função. map<int, Record> m; // ... // Obtém uma referência para um item; Record &r = m[1] // Retorna uma referência que logo será inválida, // pois a memória será liberada. return r; }Abaixo você pode ver um código semelhante em Rust. Nesse caso, a linguagem detecta que um ponteiro inválido está sendo retornado e gera um erro de compilação. Repare bem, é um erro de compilação e não de execução.
{ let mut m = HashMap::new(); // ... let r = m.get(1); // Erro de compilação. return r; }
Structs
Em Rust, pode-se definir structs (que contém os dados) e adicionar funções a essas structs.// Define a estrutura. struct Rectangle { width: int, height: int } // Adiciona métodos impl Rectangle { fn area(&self) -> int { // Não é necessário usar o "return". self.width * self.height } }
Extension methods
Em C# "extension methods" permitem adicionar métodos a tipos existentes, sem ter que alterá-los ou criar uma sub-classe. Em Rust, a forma que a definição dos métodos funciona é igual aos "extension methods" do C#. Repare no exemplo acima que o método "area" recebe como parâmetro o "self", que representa o objeto ao qual ele está associado.Com isso, é possível também implementar métodos para tipos já existentes, inclusive os tipos definidos pela própria biblioteca padrão.
Traits
Os traits são semelhantes a "interface" em C# ou Java. Porém, com uma diferença: não é necessário alterar a struct original para implementar uma trait (interface). Em outras palavras, você pode definir uma trait para um tipo existente.No exemplo abaixo é definida a trait "Printable" e, a seguir, é feita a implementação dela para o "int".
trait Printable { fn print(&self); } impl Printable for int { fn print(&self) { println!("{:?}", *self) } }
Pattern matching
A funcão "match" do rust funciona como um "switch", porém bem mais avançado. O uso de pattern matching não é algo novo, linguagens como Erlang fazem uso extensivo disso, mas ao menos para mim, é uma novidade em linguagens derivadas do C.match my_number { 0 => println!("zero"), 1 | 2 => println!("one or two"), 3..10 => println!("three to ten"), _ => println!("something else") }Uma das coisas mais interessantes do match é que é obrigatório definir todos os possíveis valores para ele. Se você esquecer um valor, ocorre um erro de compilação. No exemplo abaixo o "_" significa "qualquer coisa".
enum Direction { North, East, South, West } match dir { North => println!("norte"), South => println!("sul"), _ => println!("outro") }
Não existe NULL
Quantas vezes temos que adicionar aos métodos o famoso "if (x == null) throw ..."? Isso porquê na maioria das linguagens os objetos (ponteiros) podem ser nulos.Até o invetor do Null, Tony Hoare diz "Esse foi meu erro de bilhões de dólares".
Em Rust, os ponteiros não podem ser nulos. Caso seja necessário definir uma variável que pode ou não ter valor pode-se usar o enum "Option"
let x: Option<int> = 10; match x { Some(_) => println!("tem valor"); None => println!("não tem valor"); }
Onde aprender mais?
Aqui apenas destaquei alguns pontos interessantes da linguagem, não entrei em detalhes em nenhum deles. Se você deseja aprender mais, aqui vão alguns links:- Rust for Rubyists - é um excelente tutorial do Steve Klabnik, apesar do nome, mesmo quem não conhece Ruby consegue acompanhar bem;
- Tutorial oficial - esse é um tutorial mais longo que vai apresentar os principais recursos da linguagem;
- Memory Ownership and Lifetimes - vídeo explicando muito bem o gerenciamento de memória feito pelo Rust. Aqui tem o arquivo para download com qualidade melhor.