segunda-feira, 13 de janeiro de 2014

Rust, a linguagem do futuro

Você gostaria de ter uma linguagem que permite controle em baixo nível da memória, com alto desempenho e sem os problemas do C++? Então, Rust provavelmente é a linguagem que você procura.

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.

segunda-feira, 6 de janeiro de 2014

Evitando a renderização de áreas ocultas em layouts responsivos

Atualmente é bastante comum o uso de layouts responsivos, ou seja, layouts que se adaptam ao navegador do usuário.

Se um usuário acessa o site usando um smartphone, o site provavelmente irá exibir apenas o conteúdo essencial, deixando invisível boa parte do HTML.

Agora, por que devemos renderizar as áreas que não são visíveis? Evitando a renderização de áreas que sabemos estarem ocultas podemos melhorar o desempenho e economizar bateria dos dispositivos.

Ember Responsive Area

Para o Ember.js criei um componente bastante simples que resolve esse problema. Toda a área dentro do componente será renderizada apenas se estiver visível. O componente se chama Ember Responsive Area.

O uso dele é bastante simples:
<div class="hidden-phone">
  {{#responsive-area}}
    Aqui vem o conteúdo.
  {{/responsive-area}}
</div>

Como funciona

Quando o componente é carregado, ele verifica se o elemento está visível. Se estiver visível, renderiza a área. Caso contrário, não renderiza e monitora o redimensionamento do navegador.

Se o usuário redimensionar o navegador ou girar o dispositivo (tablet/smartphone) o componente irá verificar novamente se deve renderizar a área.

Note que isso só funciona se a visibilidade da região depende do tamanho da janela do navegador, isso não funcionará se você está ocultando e exibindo manualmente a área.