DEV Community

Marcelo Castellani
Marcelo Castellani

Posted on • Updated on

Nosso amigo compilador

Dando sequência aos posts sobre Rust aqui no dev.to, vamos falar sobre compilação, afinal Rust é uma linguagem de programação compilada. Mas o que isso quer dizer?

As linguagens de programação podem ser classificadas em três categorias principais: compiladas, interpretadas e aquelas que usam bytecode. Cada uma dessas abordagens possui características distintas que afetam o processo de execução do código. Vamos explorar essas diferenças:

Linguagens compiladas são aquelas em que o código-fonte é traduzido integralmente para um código de máquina específico antes da execução. O compilador é responsável por converter todo o código-fonte em um arquivo executável, que pode ser diretamente executado pelo sistema operacional. Exemplos de linguagens compiladas incluem C, C++, Java (em alguns casos) e Rust.

A principal vantagem das linguagens compiladas é que a tradução antecipada para código de máquina resulta em uma execução geralmente mais eficiente e rápida. Além disso, erros de sintaxe e semântica são identificados durante a etapa de compilação, o que ajuda a encontrar problemas antes da execução do programa. No entanto, a compilação pode exigir mais tempo e recursos, especialmente para programas maiores.

Linguagens interpretadas são aquelas em que o código-fonte é executado linha por linha, em tempo real, por um programa chamado "interpretador". O interpretador analisa cada instrução do código-fonte e executa as ações correspondentes. Exemplos de linguagens interpretadas incluem Python, JavaScript, Ruby e PHP.

Uma das principais vantagens das linguagens interpretadas é a sua portabilidade, pois o código-fonte é independente da arquitetura do sistema. Além disso, as linguagens interpretadas são frequentemente mais flexíveis, permitindo a execução interativa e a modificação dinâmica do código em tempo de execução. No entanto, o processo de interpretação pode ser mais lento em comparação com a execução de código compilado, uma vez que as instruções são analisadas em tempo real.

Algumas linguagens utilizam uma abordagem intermediária, chamada de bytecode. Nesse modelo, o código-fonte é compilado para um formato intermediário, chamado bytecode, que é executado por uma máquina virtual específica. A máquina virtual é um programa que interpreta e executa o bytecode. Exemplos de linguagens que usam bytecode incluem Java, C#, Python (em algumas implementações) e Ruby (em algumas implementações).

O bytecode é um código de máquina simplificado e independente de plataforma, projetado para ser executado de forma eficiente pela máquina virtual. Essa abordagem combina algumas vantagens das linguagens compiladas e interpretadas. Por um lado, o bytecode é pré-compilado e pode ser executado mais rapidamente do que a interpretação direta do código-fonte. Por outro lado, a máquina virtual oferece portabilidade, pois pode ser executada em diferentes sistemas operacionais e arquiteturas.

As linguagens compiladas apresentam algumas vantagens em relação às linguagens interpretadas ou de bytecode, especialmente no que diz respeito à velocidade. Como o código-fonte é traduzido diretamente para código de máquina, as linguagens compiladas tendem a ter um desempenho melhor em termos de velocidade de execução. Isso ocorre porque o código compilado é otimizado para a arquitetura do sistema e pode aproveitar ao máximo os recursos disponíveis. Em contraste, as linguagens interpretadas precisam analisar e executar as instruções em tempo real, o que pode resultar em uma execução mais lenta.

Outro ponto muito importante é que o compilador é seu melhor amigo pois têm a capacidade de otimizar o código durante a compilação, o que pode resultar em um uso mais eficiente dos recursos do sistema. Ele também podem aplicar técnicas como inlining de funções, eliminação de código morto e otimizações de laços para melhorar o desempenho. Por outro lado, os interpretadores não têm a mesma capacidade de otimização, pois executam o código linha por linha sem uma visão completa do programa.

Além disso os compiladores verificam o código-fonte em busca de erros de sintaxe e semântica durante a etapa de compilação. Isso significa que muitos erros são identificados antes mesmo da execução do programa, o que ajuda os desenvolvedores a corrigirem problemas de forma mais eficiente. Nas linguagens interpretadas, os erros são geralmente detectados durante a execução, o que pode levar a um ciclo de desenvolvimento mais lento ou problemas em produção.

Outra vantagem é que nas linguagens compiladas, o código é transformado em um executável que pode ser implantado e executado em qualquer sistema operacional compatível. Isso facilita a distribuição do software, pois os usuários não precisam ter o compilador ou interpretador instalado em suas máquinas. Em contraste, nas linguagens interpretadas ou de bytecode, os usuários precisam ter o interpretador correspondente instalado para executar o código (por exemplo um interpretador JavaScript ou o .NET ou o JRE).

É importante ressaltar que cada abordagem tem suas próprias vantagens e desvantagens, e a escolha entre linguagens compiladas, interpretadas ou de bytecode depende das necessidades específicas do projeto e dos compromissos que os desenvolvedores estão dispostos a fazer em termos de velocidade, facilidade de desenvolvimento e outros fatores.

O compilador do rust, nosso melhor amigo

Um dos grades trunfos do Rust é o seu compilador, que além das vantagens apresentadas, realiza diversas transformações e verificações antes efetivamente rodar seu código. Mais do que isso, ele lhe dá mensagens claras e efetivas do que está errado, por que está errado e onde corrigir.

A imagem apresenta um erro reportado pelo compilador do rust ao tentar atribuir uma sequência de caracteres a uma variável do tipo inteiro

Além dessa mensagem clara e direta do que está errado e por que, ele ainda permite usar a diretiva explain para detalhar melhor o erro. No caso da imagem, nosso erro é o E0303. Dessa forma basta rodar o comando a seguir:

$ rustc --explain E0308
Enter fullscreen mode Exit fullscreen mode

E um retorno detalhado do problema será apresentado:

Expected type did not match the received type.

Erroneous code examples:


fn plus_one(x: i32) -> i32 {
    x + 1
}

plus_one("Not a number");
//       ^^^^^^^^^^^^^^ expected `i32`, found `&str`

if "Not a bool" {
// ^^^^^^^^^^^^ expected `bool`, found `&str`
}

let x: f32 = "Not a float";
//     ---   ^^^^^^^^^^^^^ expected `f32`, found `&str`
//     |
//     expected due to this


This error occurs when an expression was used in a place where the compiler
expected an expression of a different type. It can occur in several cases, the
most common being when calling a function and passing an argument which has a
different type than the matching type in the function declaration.
Enter fullscreen mode Exit fullscreen mode

Ou seja, além de não deixar o código problemático ir para produção validando antes de o executar, ele ainda pode ser uma fonte incrível de aprendizado.

Como aprender Rust?

Nessa série de artigos que estou publicando aqui no dev.to vou apresentar a linguagem Rust do zero, desde a instalação do ferramental necessário e configuração do editor de textos até a criação de uma API simples usando bibliotecas de mercado.
 
Se você se interessa por Rust seja por novas oportunidades profissionais, seja apenas pra aprender algo novo, me siga aqui ou no meu Twitter. Planejo publicar um artigo a cada dois dias, se tudo der certo. 

Você também pode dar uma olhada no material oficial da linguagem, com acesso gratuito aqui. Ou no meu livro, escrito junto com meu amigo de longa data Willian Molinari, que está em sua segunda edição.

Também tem o sensacional livro Programming Rust, que cobre a linguagem de ponta a ponta além de diversos outros pontos em suas mais de setecentas páginas. 

Aprender uma nova linguagem de programação pode ser desafiador e excitante. Espero que embarque nessa viagem comigo.

Top comments (0)