quinta-feira, 18 de fevereiro de 2021

Programando um jogo de adivinhação em Rust

Vamos pular para o Rust trabalhando juntos em um projeto prático! Este capítulo apresenta alguns conceitos comuns do Rust, mostrando como usá-los em um programa real. Você aprenderá sobre let, match, métodos, funções associadas, uso de crates externos e muito mais! Os capítulos seguintes explorarão essas idéias com mais detalhes. Neste capítulo, você praticará os fundamentos.

Implementaremos um problema clássico de programação para iniciantes: um jogo de adivinhação. Funciona assim: o programa irá gerar um número inteiro aleatório entre 1 e 100. Em seguida, ele solicitará que o jogador insira uma estimativa. Depois de inserir uma estimativa, o programa indicará se a estimativa é muito baixa ou muito alta. Se o palpite estiver correto, o jogo irá imprimir uma mensagem de parabéns e sair.

Configurando um Novo Projeto

Para configurar um novo projeto, vá para o diretório de projetos que você criou no Capítulo 1 e faça um novo projeto usando Cargo, assim:

$ cargo new guessing_game
$ cd guessing_game

O primeiro comando, cargo new leva o nome do projeto (guessing_game) como o primeiro argumento. O segundo comando muda para o diretório do novo projeto.

Veja o arquivo Cargo.toml gerado:

Nome do arquivo: Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

Se as informações do autor que o Cargo obteve do seu ambiente não estiverem corretas, corrija-as no arquivo e salve-o novamente.

Como você viu no Capítulo 1, cargo new gera um programa "Hello, world!" para você. Verifique o arquivo src/main.rs:

Nome do arquivo: src/main.rs

fn main() {
    println!("Hello, world!");
}

Agora vamos compilar este programa Hello, world! e executa-lo na mesma etapa usando o comando cargo run:

$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 1.50s
        Running `target/debug/guessing_game`
Hello, world!

O comando run é útil quando você precisa iterar rapidamente em um projeto, como faremos neste jogo, testando rapidamente cada iteração antes de passar para a próxima.

Reabra o arquivo src/main.rs. Você escreverá todo o código neste arquivo.

Processando um palpite

A primeira parte do programa jogo de adivinhação solicitará a entrada do usuário, processará essa entrada e verificará se a entrada está na forma esperada. Para começar, vamos permitir que o jogador dê um palpite. Digite o código na Listagem 2-1 em src/main.rs.

Nome do arquivo: src/main.rs

use std::io;

fn main() {
    println!("Adivinhe o número!");

    println!("Por favor entre com o seu palpite.");

    let mut palpite = String::new();

    io::stdin()
        .read_line(&mut palpite)
        .expect("Falha ao ler a linha.");

    println!("Seu palpite: {}", palpite);
}

Listagem 2-1: Código que obtém um palpite do usuário e a imprime

Este código contém muitas informações, então vamos examiná-lo linha por linha. Para obter a entrada do usuário e, em seguida, imprimir o resultado como saída, precisamos trazer a biblioteca io (entrada/saída) para o escopo. A biblioteca io vem da biblioteca padrão (conhecida como std):

use std::io;

Por padrão, Rust traz apenas alguns tipos no âmbito de cada programa no prelúdio. Se um tipo que você deseja usar não está no prelúdio, você deve trazer esse tipo para o escopo explicitamente com uma declaração use. O uso da biblioteca std::io fornece vários recursos úteis, incluindo a capacidade de aceitar entradas do usuário.

Como você viu no Capítulo 1, a função main é o ponto de entrada para o programa:

fn main() {

A sintaxe fn declara uma nova função, os parênteses, (), indicam que não há parâmetros, e a chave, {, inicia o corpo da função.

Como você também aprendeu no Capítulo 1, println! é uma macro que imprime uma string na tela:

println!("Adivinhe o número!");

println!("Por favor entre com o seu palpite.");

Este código está imprimindo um prompt informando o que é o jogo e solicitando a entrada do usuário.

Armazenamento de valores com variáveis

A seguir, criaremos um local para armazenar a entrada do usuário, como este:

let mut palpite = String::new();

Agora o programa está ficando interessante! Há muita coisa acontecendo nesta pequena linha. Observe que esta é uma instrução let, que é usada para criar uma variável. Aqui está outro exemplo:

let foo = bar;

Esta linha cria uma nova variável chamada foo e a vincula ao valor da variável bar. No Rust, as variáveis são imutáveis por padrão. Discutiremos esse conceito em detalhes na seção “Variáveis e mutabilidade” no Capítulo 3. O exemplo a seguir mostra como usar mut antes do nome da variável para tornar uma variável mutável:

let foo = 5; // imutável
let mut bar = 5; // mutável

Nota: // inicia um comentário que continua até o final da linha. Rust ignora tudo nos comentários, que são discutidos com mais detalhes no Capítulo 3.

Voltemos ao programa de jogo de adivinhação. Agora você sabe que let mut palpite introduzirá uma variável mutável chamada palpite. Do outro lado do sinal de igual (=) está o valor vinculado a palpite, que é o resultado da chamada String::new, uma função que retorna uma nova instância de String. String é um tipo de string fornecido pela biblioteca padrão que é um bit de texto codificado em UTF-8 que pode ser ampliado.

A sintaxe :: na linha ::new indica que new é uma função associada do tipo String. Uma função associada é implementada em um tipo, neste caso String, ao invés de em uma instância particular de String. Algumas linguagens chamam isso de método estático.

Esta função new cria uma nova string vazia. Você encontrará uma função new em muitos tipos, porque é um nome comum para uma função que cria um novo valor de algum tipo.

Para resumir, a linha let mut palpite = String::new(); criou uma variável mutável que está atualmente associada a uma nova instância vazia de String. Uau!

Lembre-se de que incluímos a funcionalidade de entrada/saída da biblioteca padrão use std::io; na primeira linha do programa. Agora vamos chamar a função stdin do módulo io:

io::stdin()
    .read_line(&mut palpite)

Se não tivéssemos colocado a linha use std::io no início do programa, poderíamos ter escrito essa chamada de função como std::io::stdin. A função stdin retorna uma instância de std::io::Stdin, que é um tipo que representa um identificador para a entrada padrão do seu terminal.

A próxima parte do código, .read_line(&mut palpite) chama o método read_line no identificador de entrada padrão para obter a entrada do usuário. Também estamos passando um argumento para read_line: &mut palpite.

O trabalho de read_line é pegar tudo o que o usuário digitar na entrada padrão e colocá-lo em uma string, de modo que essa string seja um argumento. O argumento da string precisa ser mutável para que o método possa alterar o conteúdo da string adicionando a entrada do usuário.

& indica que esse argumento é uma referência, o que fornece uma maneira de permitir que várias partes do seu código acessem um dado sem a necessidade de copiar esses dados para a memória várias vezes. As referências são um recurso complexo e uma das principais vantagens do Rust é o quão seguro e fácil é usar as referências. Você não precisa saber muitos desses detalhes para terminar este programa. Por enquanto, tudo que você precisa saber é que, como as variáveis, as referências são imutáveis ​​por padrão. Portanto, você precisa escrever &mut palpite em vez de &palpite para torná-lo mutável. (O Capítulo 4 explicará as referências mais detalhadamente.)

Lidando com a falha potencial com o tipo Result

Ainda estamos trabalhando nesta linha de código. Embora agora estejamos discutindo uma terceira linha de texto, ela ainda faz parte de uma única linha lógica de código. A próxima parte é este método:

.expect("Falha ao ler a linha.");

Quando você chama um método com a sintaxe .foo(), geralmente é aconselhável introduzir uma nova linha e outro espaço em branco para ajudar a quebrar as linhas longas. Poderíamos ter escrito este código como:

io::stdin().read_line(&mut palpite).expect("Falha ao ler a linha.");

No entanto, uma linha longa é difícil de ler, por isso é melhor dividi-la. Agora vamos discutir o que esta linha faz.

Conforme mencionado anteriormente, read_line coloca o que o usuário digitar na string que estamos passando como parametro para a função, mas também retorna um valor - neste caso, um io::Result. Rust tem vários tipos nomeados Result em sua biblioteca padrão Result versões genéricas e específicas para submódulos, como io::Result.

Os tipos Result são enumerações, geralmente chamadas de enums. Uma enumeração é um tipo que pode ter um conjunto fixo de valores, e esses valores são chamados de variantes enum. O Capítulo 6 abordará enums com mais detalhes.

Pois Result, as variantes são Ok ou Err. A variante Ok indica que a operação foi bem-sucedida e dentro de Ok está o valor gerado com sucesso. A variante Err significa que a operação falhou e Err contém informações sobre como ou por que a operação falhou.

O objetivo desses tipos Result é codificar informações de tratamento de erros. Valores do tipo Result, como valores de qualquer tipo, têm métodos definidos neles. Uma instância de io::Result possui um método expect que você pode chamar. Se esta instância de io::Result for um valor Err, expect fará com que o programa trave e exiba a mensagem que você transmitiu como argumento expect. Se o método read_line retornar um Err, provavelmente será o resultado de um erro proveniente do sistema operacional subjacente. Se esta instância de io::Result for um valor Ok, expect assumirá o valor de retorno que Ok está mantendo e retorna apenas esse valor para que você possa usá-lo. Nesse caso, esse valor é o número de bytes em que o usuário inseriu na entrada padrão.

Se você não chamar expect, o programa será compilado, mas você receberá um aviso:

$ cargo build
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
warning: unused `std::result::Result` that must be used
  --> src/main.rs:10:5
   |
10 |     io::stdin().read_line(&mut palpite);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
    

Rust avisa que você não usou o valor Result retornado pelo método read_line, indicando que o programa não tratou um possível erro.

A maneira correta de suprimir o aviso é escrever o tratamento de erros, mas como você deseja apenas travar este programa quando ocorrer um problema, você pode usar expect. Você aprenderá a se recuperar de erros no Capítulo 9.

Impressão de valores com marcadores de posição println!

Além da chave de fechamento, há apenas mais uma linha para discutir no código adicionado até agora, que é o seguinte:

println!("Seu palpite: {}", palpite);

Esta linha imprime a string em que salvamos a entrada do usuário. O conjunto de chaves, {}, é um espaço reservado: pense em pequenas pinças de caranguejo que mantêm um valor no lugar. Você pode imprimir mais de um valor usando chaves: o primeiro conjunto de chaves contém o primeiro valor listado após a string de formato, o segundo conjunto contém o segundo valor e assim por diante. Imprimir vários valores em uma chamada para println! ficaria assim:

let x = 5;
let y = 10;

println!("x = {} e y = {}", x, y);

Este código vai imprimir x = 5 e y = 10.

Testando a Primeira Parte

Vamos testar a primeira parte do jogo de adivinhação. Execute-o usando cargo run:

C:\Users\user\projetos\guessing_game>cargo run
   Compiling guessing_game v0.1.0 (C:\Users\user\projetos\guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 22.74s
     Running `target\debug\guessing_game.exe`
Adivinhe o número!
Por favor entre com o seu palpite.
9
Seu palpite: 9
    

Neste ponto, a primeira parte do jogo está concluída: estamos recebendo dados do teclado e depois imprimindo.

Gerando um Número Secreto

Em seguida, precisamos gerar um número secreto que o usuário tentará adivinhar. O número secreto deve ser diferente a cada vez, para que seja divertido jogar mais de uma vez. Vamos usar um número aleatório entre 1 e 100 para que o jogo não seja muito difícil. Rust ainda não inclui a funcionalidade de número aleatório em sua biblioteca padrão. No entanto, a equipe Rust fornece o crate rand.

Usando um crate para obter mais funcionalidade

Lembre-se de que um crate é uma coleção de arquivos de código-fonte do Rust. O projeto que estamos construindo é um crate binário, que é um executável. O crate rand é uma crate de biblioteca, que contém código destinado a ser usado em outros programas.

O uso de crates externos pelo cargo é onde ele realmente brilha. Antes que possamos escrever o código que usa rand, precisamos modificar o arquivo Cargo.toml para incluir o crate rand como uma dependência. Abra esse arquivo agora e adicione a seguinte linha na parte inferior, abaixo do cabeçalho [dependencies] da seção que o Cargo criou para você:

Nome do arquivo: Cargo.toml

[dependencies]
rand = "0.5.5"

No arquivo Cargo.toml, tudo o que segue um cabeçalho é parte de uma seção que continua até que outra seção seja iniciada. A seção [dependencies] é onde você diz ao Cargo de quais crates externos seu projeto depende e de quais versões desses crates você precisa. Nesse caso, especificaremos o crate rand com o especificador de versão semântica 0.5.5. Cargo entende o Controle de Versão Semântico (às vezes chamado de SemVer), que é um padrão para escrever números de versão. O número 0.5.5 é, na verdade, uma abreviação de ^0.5.5, o que significa qualquer versão que esteja no mínimo 0.5.5 abaixo de 0.6.0. Cargo considera que essas versões têm APIs públicas compatíveis com a versão 0.5.5.

Agora, sem alterar nenhum código, vamos construir o projeto, conforme mostrado na Listagem 2-2.

C:\Users\user\projetos\guessing_game>cargo build
    Updating crates.io index
  Downloaded rand v0.5.6
  Downloaded rand_core v0.3.1
  Downloaded rand_core v0.4.2
  Downloaded winapi v0.3.9
  Downloaded 4 crates (1.4 MB) in 4.38s (largest was `winapi` at 1.2 MB)
   Compiling winapi v0.3.9
   Compiling rand_core v0.4.2
   Compiling rand_core v0.3.1
   Compiling rand v0.5.6
   Compiling guessing_game v0.1.0 (C:\Users\user\projetos\guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 2m 18s
    

Listagem 2-2: A saída da execução cargo build após adicionar o crate rand como uma dependência

Você pode ver números de versão diferentes (mas todos eles serão compatíveis com o código, graças ao SemVer!), Linhas diferentes (dependendo do sistema operacional) e as linhas podem estar em uma ordem diferente.

Agora que temos uma dependência externa, o Cargo busca as versões mais recentes de tudo no registro, que é uma cópia dos dados de Crates.io. Crates.io é onde as pessoas no ecossistema Rust publicam seus projetos Rust de código aberto para outros usarem.

Depois de atualizar o registro, o Cargo verifica a seção [dependencies] e baixa todos os crates que você ainda não tiver. Neste caso, apesar de listarmos apenas rand como dependência, Cargo também baixou libc e rand_core, porque rand depende deles para funcionar. Depois de baixar os crates, Rust os compila e então compila o projeto com as dependências disponíveis.

Se você executar imediatamente cargo build novamente sem fazer nenhuma alteração, não obterá nenhuma saída além da linha Finished. O Cargo sabe que já baixou e compilou as dependências e você não alterou nada sobre elas no arquivo Cargo.toml. O Cargo também sabe que você não mudou nada em seu código, então também não o recompila. Sem nada para fazer, ele simplesmente sai.

Se você abrir o arquivo src/main.rs, fizer uma alteração trivial, salvá-lo e compilá-lo novamente, verá apenas duas linhas de saída:

$ cargo build
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
    

Essas linhas mostram que o Cargo atualiza apenas a construção com sua pequena alteração no arquivo src/main.rs. Suas dependências não mudaram, então Cargo sabe que pode reutilizar o que já baixou e compilou para elas. Ele apenas reconstrói sua parte do código.

Garantindo construções reproduzíveis com o arquivo Cargo.lock

O Cargo tem um mecanismo que garante que você possa reconstruir o mesmo artefato sempre que você ou qualquer outra pessoa criar seu código: o Cargo usará apenas as versões das dependências especificadas até que você indique o contrário. Por exemplo, o que acontecerá se na próxima semana a versão 0.5.6 do crate rand for lançada e contiver uma correção de bug importante, mas também contiver uma regressão que quebrará seu código?

A resposta para esse problema é o arquivo Cargo.lock, que foi criado na primeira vez que você executou cargo builde em seu diretório guessing_game. Quando você constrói um projeto pela primeira vez, o Cargo descobre todas as versões das dependências que atendem aos critérios e as grava no arquivo Cargo.lock. Quando você construir seu projeto no futuro, Cargo verá que o arquivo Cargo.lock existe e usará as versões especificadas lá em vez de fazer todo o trabalho de descobrir as versões novamente. Isso permite que você tenha uma construção reproduzível automaticamente. Em outras palavras, seu projeto permanecerá em 0.5.5 até que você atualize explicitamente, graças ao arquivo Cargo.lock.

Atualizando um crate para obter uma nova versão

Quando você não deseja atualizar um crate, cargo fornece outro comando, update que irá ignorar o arquivo Cargo.lock e descobrir todas as últimas versões que atendem às suas especificações em Cargo.toml. Se funcionar, o Cargo gravará essas versões no arquivo Cargo.lock.

Mas por padrão, o Cargo procurará apenas versões maiores que 0.5.5 e menores que 0.6.0. Se o crate rand lançou duas novas versões 0.5.6 e 0.6.0, você veria o seguinte se executasse cargo update:

$ cargo update
    Updating crates.io index
    Updating rand v0.5.5 -> v0.5.6
    

Neste ponto, você também notaria uma mudança em seu arquivo Cargo.lock, observando que a versão do crate rand que você está usando agora é 0.5.6.

Se você quiser usar a versão do rand 0.6.0 ou qualquer versão da série 0.6.x, terá que atualizar o arquivo Cargo.toml para ficar assim:

[dependencies]
rand = "0.6.0"

Na próxima vez que você executar cargo build, o Cargo atualizará o registro de crates disponíveis e reavaliará seus requisitos rand de acordo com a nova versão que você especificou.

Há muito mais a dizer sobre Cargo e seu ecossistema, que discutiremos no Capítulo 14, mas por enquanto, isso é tudo que você precisa saber. O Cargo facilita a reutilização de bibliotecas, de modo que os Rustáceos são capazes de escrever projetos menores que são montados a partir de vários pacotes.

Gerando um Número Aleatório

Agora que você adicionou crate rand ao arquivo Cargo.toml, vamos começar a usar rand. A próxima etapa é atualizar src/main.rs, conforme mostrado na Listagem 2-3.

Nome do arquivo: src/main.rs

use std::io;
use rand::Rng;

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("O número secreto é: {}", numero_secreto);

    println!("Por favor entre com o seu palpite.");

    let mut palpite = String::new();

    io::stdin()
        .read_line(&mut palpite)
        .expect("Falha ao ler a linha.");

    println!("Seu palpite: {}", palpite);
}

Listagem 2-3: Adicionando código para gerar um número aleatório

Primeiro, adicione uma linha use: use rand::Rng. A característica Rng define métodos que os geradores de números aleatórios implementam, e essa característica deve estar no escopo para que possamos usar esses métodos. O Capítulo 10 abordará as características em detalhes.

Em seguida, estamos adicionando duas linhas no meio. A função rand::thread_rng nos dará o gerador de número aleatório específico que vamos usar: um que é local para o thread atual de execução e propagado pelo sistema operacional. Em seguida, chamamos o método gen_range no gerador de números aleatórios. Esse método é definido pela característica Rng que colocamos no escopo com a instrução use rand::Rng. O método gen_range recebe dois números como argumentos e gera um número aleatório entre eles. É inclusivo no limite inferior, mas exclusivo no limite superior, portanto, precisamos especificar 1 e 101 para solicitar um número entre 1 e 100.

Nota: Você não saberá apenas quais características usar e quais métodos e funções chamar de uma crate. As instruções para usar uma crate estão na documentação de cada crate. Outro recurso interessante do Cargo é que você pode executar o comando cargo doc --open, que criará a documentação fornecida por todas as suas dependências localmente e abri-la em seu navegador. Se você estiver interessado em outras funcionalidades da crate rand, por exemplo, execute cargo doc --open e clique rand na barra lateral à esquerda.

A segunda linha que adicionamos no meio do código imprime o número secreto. Isso é útil enquanto estamos desenvolvendo o programa para poder testá-lo, mas vamos excluí-lo da versão final. Não será um grande jogo se o programa imprimir a resposta assim que for iniciado!

Tente executar o programa algumas vezes:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/guessing_game`
Adivinhe o número!
O número secreto é: 75
Por favor entre com o seu palpite.
9
Seu palpite: 9

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/guessing_game`
Adivinhe o número!
O número secreto é: 46
Por favor entre com o seu palpite.
2
Seu palpite: 2
    

Você deve obter diferentes números aleatórios, e todos devem ser números entre 1 e 100. Ótimo trabalho!

Comparando o palpite com o número secreto

Agora que temos a entrada do usuário e um número aleatório, podemos compará-los. Essa etapa é mostrada na Listagem 2-4. Observe que este código ainda não compilará, como explicaremos.

Nome do arquivo: src/main.rs

Esse código não compila Esse código não compila.

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    // --recorte--

    println!("Seu palpite: {}", palpite);

    match guess.cmp(&numero_secreto) {
        Ordering::Less => println!("Muito baixo!"),
        Ordering::Greater => println!("Muito alto!"),
        Ordering::Equal => println!("Você ganhou!"),
    }
}

Listagem 2-4: Lidando com os possíveis valores de retorno da comparação de dois números

O primeiro novo pedaço aqui é outra instrução use, trazendo um tipo chamado std::cmp::Ordering para o escopo da biblioteca padrão. Como Result, Ordering é outro enum, mas as variantes de Ordering são Less (menor), Greater (maior) e Equal (igual). Esses são os três resultados possíveis quando você compara dois valores.

Em seguida, adicionamos cinco novas linhas na parte inferior que usam o tipo Ordering. O método cmp compara dois valores e pode ser chamado em qualquer coisa que possa ser comparada. Ele faz referência a tudo o que você deseja comparar: aqui está comparando o palpite com o numero_secreto. Em seguida, ele retorna uma variante enum do Ordering que incluímos no escopo com a instrução use. Usamos uma expressão match para decidir o que fazer a seguir com base em qual variante de Ordering foi retornada da chamada de cmp com os valores em palpite e numero_secreto.

Uma expressão match é feita de ramificações. Uma ramificação consiste em um padrão e no código que deve ser executado se o valor dado ao início da expressão match se ajustar ao padrão dessa ramificação. Rust pega o valor dado a match e examina o padrão de cada ramificação sucessivamente. A construção match e os padrões são recursos poderosos no Rust que permitem expressar uma variedade de situações que seu código pode encontrar e certificar-se de lidar com todas elas. Esses recursos serão abordados em detalhes no Capítulo 6 e Capítulo 18, respectivamente.

Vamos examinar um exemplo do que aconteceria com a expressão match usada aqui. Digamos que o usuário digitou 50 e o número secreto gerado aleatoriamente é 38. Quando o código comparar 50 com 38, o método cmp retornará Ordering::Greater, porque 50 é maior que 38. A expressão match obtém o valor Ordering::Greater e começa a verificar o padrão de cada ramificação. Ele examina o padrão da primeira ramificação Ordering::Less, e vê que o valor Ordering::Greater não corresponde a Ordering::Less, então ele ignora o código naquela ramificação e passa para a próxima ramificação. O padrão da próxima ramificação Ordering::Greater, corresponde a Ordering::Greater! O código associado naquela ramificação será executado e impresso Muito alto! na tela. A expressão match termina porque não há necessidade de olhar para a última ramificação neste cenário.

No entanto, o código na Listagem 2-4 ainda não será compilado. Vamos tentar:

$ cargo run
   Compiling guessing_game v0.1.0 (/home/thor/Documentos/Rust/guessing_game)
error[E0308]: mismatched types
  --> src/main.rs:22:23
   |
22 |     match palpite.cmp(&numero_secreto) {
   |                       ^^^^^^^^^^^^^^^ expected struct `String`, found integer
   |
   = note: expected reference `&String`
              found reference `&{integer}`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `guessing_game`

To learn more, run the command again with --verbose.
    

O núcleo do erro afirma que existem tipos incompatíveis. Rust tem um sistema de tipo estático forte. No entanto, ele também possui inferência de tipo. Quando escrevemos let mut palpite = String::new(), Rust foi capaz de inferir que palpite deveria ser uma String e não nos fez escrever o tipo. O numero_secreto, por outro lado, é um tipo de número. Alguns tipos de número podem ter um valor entre 1 e 100: i32, um número de 32 bits; u32, um número de 32 bits sem sinal; i64, um número de 64 bits; assim como outros. O padrão de Rust é um i32, que é o tipo de numero_secreto, a menos que você adicione informações de tipo em outro lugar que façam Rust inferir um tipo numérico diferente. O motivo do erro é que Rust não pode comparar uma string e um tipo de número.

Em última análise, queremos converter a String inserida no programa como entrada em um tipo de número real para que possamos compará-lo numericamente ao número secreto. Podemos fazer isso adicionando outra linha ao corpo da função main:

Nome do arquivo: src/main.rs

use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("O número secreto é: {}", numero_secreto);

    println!("Por favor entre com o seu palpite.");

    let mut palpite = String::new();

    io::stdin()
        .read_line(&mut palpite)
        .expect("Falha ao ler a linha.");

    let palpite: u32 = palpite.trim().parse().expect("Por favor entre com um número!");

    println!("Seu palpite: {}", palpite);

    match palpite.cmp(&numero_secreto) {
        Ordering::Less => println!("Muito baixo!"),
        Ordering::Greater => println!("Muito alto!"),
        Ordering::Equal => println!("Você ganhou!"),
    }
}

A linha é:

let palpite: u32 = palpite.trim().parse().expect("Por favor entre com um número!");

Criamos uma variável chamada palpite. Mas espere, o programa já não tem uma variável chamada palpite? Sim, mas Rust nos permite obscurecer o valor anterior de palpite com um novo. Este recurso é frequentemente usado em situações nas quais você deseja converter um valor de um tipo para outro. O sombreamento nos permite reutilizar o nome palpite da variável em vez de nos forçar a criar duas variáveis exclusivas, como palpite_str e palpite por exemplo. (O Capítulo 3 cobre o sombreamento com mais detalhes.)

Nos ligamos palpite à expressão palpite.trim().parse(). O palpite na expressão refere-se variável original palpite que foi uma String que recebemos como entrada. O método trim em uma instância String eliminará qualquer espaço em branco no início e no final. Embora u32 possa conter apenas caracteres numéricos, o usuário deve pressionar enter para satisfazer read_line. Quando o usuário pressiona enter, um caractere de nova linha é adicionado à string. Por exemplo, se o usuário digita 5 e presionar enter para entrar, palpite se parece com isso: 5\n. O \n representa “nova linha”, o resultado de pressionar enter. O método trim elimina \n, resultando em apenas 5.

O método parse em strings converte uma string em algum tipo de número. Como esse método podemos converter uma variedade de tipos de número, precisamos informar a Rust o tipo de número exato que queremos usar let palpite: u32. Os dois pontos (:) depois de palpite informar a Rust que anotaremos o tipo da variável. Rust tem alguns tipos de números integrados; o u32 visto aqui é um inteiro sem sinal de 32 bits. É uma boa escolha padrão para um pequeno número positivo. Você aprenderá sobre outros tipos de número no Capítulo 3. Além disso, a anotação u32 neste programa de exemplo e a comparação com numero_secreto significa que Rust inferirá que numero_secreto também deve ser um u32. Portanto, agora a comparação será entre dois valores do mesmo tipo!

A chamada para parse pode facilmente causar um erro. Se, por exemplo, a string continha A👍%, não haveria como convertê-la em um número. Como pode falhar, o método parse retorna um tipo Result, da mesma forma que o método read_line (discutido anteriormente em “Lidando com a falha potencial com o tipo Result”). Vamos tratar Result da mesma maneira, usando o método expect novamente. Se parse retornar um variante Err Result porque não foi possível criar um número a partir da string, a chamada expect travará o jogo e imprimirá a mensagem que fornecemos. Se parse puder converter com sucesso a string em um número, ele retornará a variante Ok de Result e expect o número que desejamos do valor Ok.

Vamos rodar o programa agora!

$ cargo run
   Compiling guessing_game v0.1.0 (/home/thor/Documentos/Rust/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.67s
     Running `target/debug/guessing_game`
Adivinhe o número!
O número secreto é: 9
Por favor entre com o seu palpite.
8
Seu palpite: 8
Muito baixo!
    

Legal! Mesmo que espaços tenham sido adicionados antes da estimativa, o programa ainda descobriu que o usuário adivinhou 76. Execute o programa algumas vezes para verificar o comportamento diferente com diferentes tipos de entrada: adivinhe o número corretamente, adivinhe um número muito alto, e adivinhe um número muito baixo.

Temos a maior parte do jogo funcionando agora, mas o usuário só pode tentar adivinhar o número uma vez. Vamos mudar isso adicionando um loop!

Permitindo várias tentativas com Looping

A palavra-chave loop cria um loop infinito. Vamos adicionar isso agora para dar aos usuários mais chances de adivinhar o número:

Nome do arquivo: src/main.rs

use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("O número secreto é: {}", numero_secreto);

    loop {

        println!("Por favor entre com o seu palpite.");

        let mut palpite = String::new();

        io::stdin()
            .read_line(&mut palpite)
            .expect("Falha ao ler a linha.");

        let palpite: u32 = palpite.trim().parse().expect("Por favor entre com um número!");

        println!("Seu palpite: {}", palpite);

        match palpite.cmp(&numero_secreto) {
            Ordering::Less => println!("Muito baixo!"),
            Ordering::Greater => println!("Muito alto!"),
            Ordering::Equal => println!("Você ganhou!"),
        }
    }
}

Como você pode ver, movemos tudo do prompt de entrada de adivinhação em diante para dentro de loop. Certifique-se de recuar as linhas dentro do loop mais quatro espaços cada e execute o programa novamente. Observe que há um novo problema porque o programa está fazendo exatamente o que dissemos para ele fazer: peça outro palpite para sempre! Não parece que o usuário pode sair!

O usuário sempre pode interromper o programa usando o atalho de teclado ctrl-c. Mas há outra maneira de escapar desse monstro insaciável, conforme mencionado na discussão parse em “Comparando a Suposição com o Número Secreto”: se o usuário digitar uma resposta não numérica, o programa irá travar. O usuário pode tirar vantagem disso para sair, conforme mostrado aqui:

>cargo run
   Compiling guessing_game v0.1.0 (C:\Users\user\projetos\guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 27.11s
     Running `target\debug\guessing_game.exe`
Adivinhe o número!
O número secreto é: 91
Por favor entre com o seu palpite.
76
Seu palpite: 76
Muito baixo!
Por favor entre com o seu palpite.
100
Seu palpite: 100
Muito alto!
Por favor entre com o seu palpite.
91
Seu palpite: 91
Você ganhou!
Por favor entre com o seu palpite.
sair
thread 'main' panicked at 'Por favor entre com um número!: ParseIntError { kind: InvalidDigit }', src\main.rs:22:51
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\guessing_game.exe` (exit code: 101)
    

Digitando sair realmente fecha o jogo, mas o mesmo acontecerá com qualquer outra entrada não numérica. No entanto, isso não é ideal para dizer o mínimo. Queremos que o jogo pare automaticamente quando o número correto for adivinhado.

Sair após um palpite correto

Vamos programar o jogo para encerrar quando o usuário vencer, adicionando uma declaração break:

Nome do arquivo: src/main.rs

match palpite.cmp(&numero_secreto) {
    Ordering::Less => println!("Muito baixo!"),
    Ordering::Greater => println!("Muito alto!"),
    Ordering::Equal => {
        println!("Você ganhou!");
        break;
    }
}

Adicionar a linha break após Você ganhou! faz com que o programa saia do loop quando o usuário adivinhar o número secreto corretamente. Sair do loop também significa sair do programa, porque o loop é a última parte da função main.

Tratamento de entrada inválida

Para refinar ainda mais o comportamento do jogo, em vez de travar o programa quando o usuário insere um não-número, vamos fazer o jogo ignorar um não-número para que o usuário possa continuar adivinhando. Podemos fazer isso alterando a linha onde palpite é convertida de String para um u32, conforme mostrado na Listagem 2-5.

Nome do arquivo: src/main.rs

use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("O número secreto é: {}", numero_secreto);

    loop {

        println!("Por favor entre com o seu palpite.");

        let mut palpite = String::new();

        io::stdin()
            .read_line(&mut palpite)
            .expect("Falha ao ler a linha.");

        let palpite: u32 = match palpite.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("Seu palpite: {}", palpite);

        match palpite.cmp(&numero_secreto) {
            Ordering::Less => println!("Muito baixo!"),
            Ordering::Greater => println!("Muito alto!"),
            Ordering::Equal => {
                println!("Você ganhou!");
                break;
            }
        }
    }
}

Listagem 2-5: Ignorando um palpite não numérico e pedindo outro palpite em vez de travar o programa.

Mudar de uma chamada expect para uma expressão match é como você geralmente muda de um erro para lidar com o erro. Lembre-se de que parse retorna um tipo Result e Result é um enum que possui as variantes Ok ou Err. Estamos usando uma expressão match aqui, como fizemos com o resultado Ordering do método cmp.

Se parse for capaz de transformar a string em um número com sucesso, ele retornará o valor Ok que contém o número resultante. Esse valor Ok corresponderá ao padrão da primeira ramificação e a expressão match apenas retornará o valor num que parse produziu e colocou dentro do valor Ok. Esse número vai acabar exatamente onde o queremos na nova variável palpite que estamos criando.

Se parse não for capaz de transformar a string em um número, ele retornará um valor que contém mais informações sobre o erro. O valor não corresponde ao padrão na primeira ramificação, mas corresponde ao padrão na segunda ramificação. O sublinhado, _, é um valor genérico; neste exemplo, estamos dizendo que queremos combinar todos os valores, não importa quais informações eles tenham dentro deles. Portanto, o programa executará o código da segunda ramificação, que diz ao programa para ir para a próxima iteração e solicitar outro palpite. Portanto, efetivamente, o programa ignora todos os erros que possa encontrar!

Agora, tudo no programa deve funcionar conforme o esperado. Vamos tentar:

>cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s
     Running `target\debug\guessing_game.exe`
Adivinhe o número!
O número secreto é: 17
Por favor entre com o seu palpite.
5
Seu palpite: 5
Muito baixo!
Por favor entre com o seu palpite.
56
Seu palpite: 56
Muito alto!
Por favor entre com o seu palpite.
sair
Por favor entre com o seu palpite.
17
Seu palpite: 17
Você ganhou!
    

Impressionante! Com um pequeno ajuste final, terminaremos o jogo de adivinhação. Lembre-se de que o programa ainda está imprimindo o número secreto. Funcionou bem para o teste, mas estragou o jogo. Vamos deletar o println! que mostra o número secreto. A Listagem 2-6 mostra o código final.

Nome do arquivo: src/main.rs

use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    println!("Adivinhe o número!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    loop {

        println!("Por favor entre com o seu palpite.");

        let mut palpite = String::new();

        io::stdin()
            .read_line(&mut palpite)
            .expect("Falha ao ler a linha.");

        let palpite: u32 = match palpite.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("Seu palpite: {}", palpite);

        match palpite.cmp(&numero_secreto) {
            Ordering::Less => println!("Muito baixo!"),
            Ordering::Greater => println!("Muito alto!"),
            Ordering::Equal => {
                println!("Você ganhou!");
                break;
            }
        }
    }
}

Listagem 2-6: Código completo do jogo de adivinhação

Resumo

Neste ponto, você construiu com sucesso o jogo de adivinhação. Parabéns!

Este projeto foi uma maneira "mãos à obra" de apresentá-lo a muitos conceitos novos do Rust: let, match, métodos, funções associadas, o uso de crates externos, e muito mais. Nos próximos capítulos, você aprenderá sobre esses conceitos com mais detalhes. O Capítulo 3 cobre os conceitos que a maioria das linguagens de programação tem, como variáveis, tipos de dados e funções, e mostra como usá-los no Rust. O Capítulo 4 explora a propriedade, um recurso que torna o Rust diferente de outras linguagens. O Capítulo 5 discute estruturas e sintaxe de método, e o Capítulo 6 explica como funcionam os enums.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

Começando na linguagem de programação Rust

Vamos começar sua jornada Rust! Há muito o que aprender, mas toda jornada começa em algum lugar. Neste capítulo, discutiremos:

  • Instalando o Rust no Linux, macOS e Windows.
  • Escrever um programa que imprime Hello, world!
  • Usando cargo, o gerenciador de pacotes e sistema de compilação do Rust.

Instalação

O primeiro passo é instalar o Rust. Faremos o download do Rust através do rustup, uma ferramenta de linha de comando para gerenciar versões do Rust e ferramentas associadas. Você precisará de uma conexão com a Internet para fazer o download.

Nota: Se você preferir não usar rustup por algum motivo, consulte a página de instalação do Rust para outras opções.

As etapas a seguir instalam a versão estável mais recente do compilador Rust. As garantias de estabilidade do Rust garantem que todos os exemplos no livro que compilam continuarão a compilar com as versões mais recentes do Rust. A saída pode ser um pouco diferente entre as versões, porque o Rust geralmente melhora as mensagens de erro e avisos. Em outras palavras, qualquer versão mais recente e estável do Rust que você instalar usando essas etapas deve funcionar conforme o esperado com o conteúdo deste livro.

Notação de linha de comando

Neste capítulo e em todo o livro, mostraremos alguns comandos usados no terminal. Todas as linhas que você deve inserir em um terminal começam $. Você não precisa digitar o caractere $; Esse caracter indica o início de cada comando. As linhas que não começam com $ normalmente mostram a saída do comando anterior. Além disso, exemplos específicos do PowerShell usarão > em vez de $.

Instalando rustup em Linux ou macOS

Se você estiver usando Linux ou macOS, abra um terminal e digite o seguinte comando:

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

O comando baixa um script e inicia a instalação da ferramenta rustup, que instala a versão estável mais recente do Rust. Sua senha pode ser solicitada. Se a instalação for bem-sucedida, a seguinte linha aparecerá:

Rust is installed now. Great!

Além disso, você precisará de algum tipo de vinculador (linker). É provável que um já esteja instalado, mas quando você tenta compilar um programa Rust e obtém erros indicando que um linker não pôde ser executado, isso significa que um linker não está instalado em seu sistema e você precisará instalá-lo manualmente. Os compiladores C geralmente vêm com o linker correto. Verifique a documentação da sua plataforma para saber como instalar um compilador C. Além disso, alguns pacotes Rust comuns dependem do código C e precisarão de um compilador C. Portanto, pode valer a pena instalar um agora.

Instalando rustup no Windows

No Windows, vá para https://www.rust-lang.org/tools/install e siga as instruções para instalar o Rust. Em algum ponto da instalação, você receberá uma mensagem explicando que também precisará das ferramentas de compilação C++ para Visual Studio 2013 ou posterior. A maneira mais fácil de adquirir as ferramentas de compilação é instalar as Ferramentas de Compilação para Visual Studio 2019. Quando questionado sobre quais pacotes de trabalho instalar, certifique-se de que "Ferramentas de compilação C++" esteja selecionado e que o SDK do Windows 10 e os componentes do pacote do idioma inglês estejam incluídos.

O restante deste livro usa comandos que funcionam em cmd.exe e PowerShell. Se houver diferenças específicas, explicaremos quais usar.

Atualizando e desinstalando

Depois de instalar o Rust via rustup, atualizar para a versão mais recente é fácil. Em seu shell, execute o seguinte script de atualização:

$ rustup update

Para desinstalar o Rust e rustup, execute o seguinte script de desinstalação em seu shell:

$ rustup self uninstall

Solução de problemas

Para verificar se você instalou o Rust corretamente, abra um shell e digite esta linha:

$ rustc --version

Você deve ver o número da versão, o hash do commit e a data do commit para a versão estável mais recente que foi lançada no seguinte formato:

rustc x.y.z (abcabcabc yyyy-mm-dd)

Se você vir esta informação, você instalou o Rust com sucesso! Se você não vir essas informações e estiver no Windows, verifique se Rust está na variável do sistema %PATH%. Se tudo estiver correto e o Rust ainda não estiver funcionando, há vários lugares onde você pode obter ajuda. O mais fácil é o canal #beginners no Rust Discord oficial. Lá, você pode conversar com outros Rustáceos (um apelido bobo que demos a nós mesmos) que podem ajudá-lo. Outros excelentes recursos incluem o fórum de usuários e o Stack Overflow.

Documentação Local

A instalação do Rust também inclui uma cópia da documentação localmente, para que você possa lê-la offline. Execute rustup doc para abrir a documentação local em seu navegador.

Sempre que um tipo ou função é fornecido pela biblioteca padrão e você não tem certeza do que ele faz ou como usá-lo, use a documentação da interface de programação de aplicativo (API) para descobrir!

Hello, World!

Agora que você instalou o Rust, vamos escrever seu primeiro programa Rust. É tradicional, ao aprender uma nova linguagem de programação, escrever um pequeno programa que imprima o texto Hello, world! na tela, então faremos o mesmo aqui!

Nota: este livro pressupõe familiaridade básica com a linha de comando. O Rust não faz exigências específicas sobre sua edição ou conjunto de ferramentas ou onde seu código reside, portanto, se você preferir usar um ambiente de desenvolvimento integrado (IDE) em vez da linha de comando, sinta-se à vontade para usar a sua IDE favorita. Muitas IDEs agora têm algum grau de suporte a Rust; verifique a documentação da IDE para obter detalhes. Recentemente, a equipe Rust tem se concentrado em habilitar um ótimo suporte de IDE, e o progresso tem sido feito rapidamente nessa frente!

Criação de um diretório de projeto

Você começará criando um diretório para armazenar seu código Rust. Não importa para Rust onde seu código reside, mas para os exercícios e projetos neste livro, sugerimos criar um diretório de projetos em seu diretório inicial e manter todos os seus projetos lá.

Abra um terminal e digite os seguintes comandos para criar um diretório projects e um diretório para o Hello, world! dentro do diretório projects.

Para Linux, macOS e PowerShell no Windows, digite:

$ mkdir ~/projects
$ cd ~/projects
$ mkdir hello_world
$ cd hello_world

Para Windows CMD, insira o seguinte:

> mkdir "%USERPROFILE%\projects"
> cd /d "%USERPROFILE%\projects"
> mkdir hello_world
> cd hello_world

Escrevendo e executando um programa Rust

Em seguida, faça um novo arquivo fonte e chame-o de main.rs. Os arquivos Rust sempre terminam com a extensão .rs. Se você estiver usando mais de uma palavra no nome do arquivo, use um sublinhado para separá-los. Por exemplo, use hello_world.rs em vez de helloworld.rs.

Agora abra o arquivo main.rs que você acabou de criar e insira o código na Listagem 1-1.

Nome do arquivo: main.rs

fn main() {
    println!("Hello, world!");
}

Listagem 1-1: Um programa que imprime Hello, world!

Salve o arquivo e volte para a janela do terminal. No Linux ou macOS, digite os seguintes comandos para compilar e executar o arquivo:

$ rustc main.rs
$ ./main
Hello, world!

No Windows, digite o comando em .\main.exe vez de ./main:

> rustc main.rs
> .\main.exe
Hello, world!

Independentemente do seu sistema operacional, a string Hello, world! deve ser impressa no terminal. Se você não vir esta saída, consulte a seção “Solução de problemas” da seção Instalação para obter ajuda.

Se imprimiu Hello, world!, parabéns! Você escreveu oficialmente um programa Rust. Isso faz de você um programador Rust - bem-vindo!

Anatomia de um programa Rust

Vamos revisar em detalhes o que acabou de acontecer em seu programa "Hello, world!". Esta é a primeira peça do quebra-cabeça:

fn main() {

}

Essas linhas definem uma função em Rust. A função main é especial: é sempre o primeiro código a ser executado em cada programa executável Rust. A primeira linha declara uma função chamada main que não possui parâmetros e não retorna nada. Se houver parâmetros, eles ficarão entre parênteses ().

Além disso, observe que o corpo da função está entre colchetes {}. Rust requer isso em todos os corpos funcionais. É um bom estilo colocar o colchete de abertura na mesma linha da declaração da função, adicionando um espaço no meio.

Se você deseja manter um estilo padrão nos projetos Rust, pode usar uma ferramenta de formatação automática chamada rustfmt para formatar seu código em um estilo específico. A equipe Rust incluiu esta ferramenta com a distribuição padrão do Rust, como rustc, ela já deve estar instalada no seu computador! Verifique a documentação online para mais detalhes.

Dentro da função main está o seguinte código:

println!("Hello, world!");

Esta linha faz todo o trabalho neste pequeno programa: imprime texto na tela. Existem quatro detalhes importantes a serem observados aqui.

Primeiro, o estilo Rust é recuar com quatro espaços, não uma tabulação.

Em segundo lugar, println! chama uma macro Rust. Se, em vez disso, chamasse uma função, seria inserido como println (sem o !). Discutiremos as macros Rust com mais detalhes no Capítulo 19. Por enquanto, você só precisa saber que usar um ! significa que você está chamando uma macro em vez de uma função normal.

Terceiro, você vê a string "Hello, world!". Passamos essa string como um argumento para println! e a string é impressa na tela.

Em quarto lugar, terminamos a linha com um ponto-e-vírgula (;), o que indica que essa expressão acabou e a próxima está pronta para começar. A maioria das linhas do código Rust termina com um ponto e vírgula.

Compilar e executar são etapas separadas

Você acabou de executar um programa recém-criado, então vamos examinar cada etapa do processo.

Antes de executar um programa Rust, você deve compilá-lo usando o compilador Rust, digitando o comando rustc e passando o nome de seu arquivo de origem, como este:

$ rustc main.rs

Se você tiver experiência em C ou C++, notará que é semelhante a gcc ou clang. Depois de compilar com sucesso, o Rust gera um executável binário.

No Linux, macOS e PowerShell no Windows, você pode ver o executável digitando o comando ls em seu shell. No Linux e no macOS, você verá dois arquivos. Com o PowerShell no Windows, você verá os mesmos três arquivos que veria usando o CMD.

$ ls
main  main.rs

Com o CMD no Windows, você deve inserir o seguinte:

> dir /B %= the /B option says to only show the file names =%
main.exe
main.pdb
main.rs

Mostra o arquivo de código-fonte com a extensão .rs, o arquivo executável (main.exe no Windows, mas main em todas as outras plataformas) e, ao usar o Windows, um arquivo contendo informações de depuração com a extensão .pdb. A partir daqui, você executa o arquivo main ou main.exe, assim:

$ ./main # ou .\main.exe no Windows

Se main.rs fosse o seu programa Hello, World!, esta linha seria impressa em seu terminal: Hello, world!.

Se você está mais familiarizado com uma linguagem dinâmica, como Ruby, Python ou JavaScript, pode não estar acostumado a compilar e executar um programa como etapas separadas. Rust é uma linguagem compilada antecipadamente, o que significa que você pode compilar um programa e fornecer o executável para outra pessoa, e eles podem executá-lo mesmo sem ter o Rust instalado. Se você der a alguém um arquivo .rb, .py ou .js, eles precisam ter uma implementação Ruby, Python ou JavaScript instalada (respectivamente). Mas nessas linguagens, você só precisa de um comando para compilar e executar seu programa. Tudo é uma troca no design da linguagem.

Apenas compilar com rustc é bom para programas simples, mas conforme seu projeto cresce, você deseja gerenciar todas as opções e facilitar o compartilhamento de seu código. A seguir, apresentaremos a ferramenta Cargo, que o ajudará a escrever programas Rust do mundo real.

Olá, Cargo!

Cargo é o sistema de construção e gerenciador de pacotes do Rust. A maioria dos Rustáceos usa essa ferramenta para gerenciar seus projetos Rust porque o Cargo lida com muitas tarefas para você, como construir seu código, baixar as bibliotecas das quais seu código depende e construir essas bibliotecas. (Chamamos de bibliotecas suas dependências de código.)

Os programas Rust mais simples, como o que escrevemos até agora, não têm dependências. Então, se tivéssemos construído o projeto Hello, world! com o Cargo, ele usaria apenas a parte do Cargo que lida com a construção de seu código. Conforme você escreve programas Rust mais complexos, você adicionará dependências e, se iniciar um projeto usando Cargo, adicionar dependências será muito mais fácil de fazer.

Como a grande maioria dos projetos do Rust usa Cargo, o restante deste livro pressupõe que você também esteja usando Cargo. O Cargo vem instalado com o Rust se você usou os instaladores oficiais discutidos na seção “Instalação”. Se você instalou o Rust por algum outro meio, verifique se o Cargo está instalado inserindo o seguinte em seu terminal:

$ cargo --version

Se você vir um número de versão, você o tem! Se você vir um erro, como command not found, consulte a documentação do seu método de instalação para determinar como instalar o Cargo separadamente.

Criando um Projeto com Cargo

Vamos criar um novo projeto usando Cargo e ver como ele difere do nosso projeto original “Hello, world!”. Navegue de volta ao diretório de seus projetos (ou onde quer que você decidiu armazenar seu código). Em seguida, em qualquer sistema operacional, execute o seguinte:

$ cargo new hello_cargo
$ cd hello_cargo

O primeiro comando cria um novo diretório chamado hello_cargo. Chamamos nosso projeto de hello_cargo e Cargo cria seus arquivos em um diretório com o mesmo nome.

Vá para o diretório hello_cargo e liste os arquivos. Você verá que o Cargo gerou dois arquivos e um diretório para nós: um arquivo Cargo.toml e um diretório src com um arquivo main.rs dentro.

Ele também inicializou um novo repositório Git junto com um arquivo .gitignore. Os arquivos Git não serão gerados se você executar cargo new em um repositório Git existente; você pode substituir esse comportamento usando cargo new --vcs=git.

Nota: Git é um sistema de controle de versão comum. Você pode mudar cargo new para usar um sistema de controle de versão diferente ou nenhum sistema de controle de versão usando o sinalizador --vcs. Execute cargo new --help para ver as opções disponíveis.

Abra Cargo.toml no editor de texto de sua escolha. Deve ser semelhante ao código da Listagem 1-2.

Nome do arquivo: Cargo.toml

[package]
name = "hello_cargo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

[dependencies]

Listagem 1-2: Conteúdo de Cargo.toml gerado por cargo new

Este arquivo está no formato TOML (Tom's Obvious, Minimal Language), que é o formato de configuração do Cargo.

A primeira linha, [package] é um título de seção que indica que as instruções a seguir estão configurando um pacote. À medida que adicionarmos mais informações a este arquivo, adicionaremos outras seções.

As próximas quatro linhas definem as informações de configuração de que o Cargo precisa para compilar seu programa: o nome, a versão, quem o escreveu e a edição do Rust a ser usada. O Cargo obtém seu nome e informações de e-mail de seu ambiente, então se essas informações não estiverem corretas, corrija as informações agora e salve o arquivo. Falaremos sobre a chave de edition no Apêndice E.

A última linha, [dependencies] é o início de uma seção para você listar qualquer uma das dependências do seu projeto. No Rust, os pacotes de código são chamados de crates. Não precisaremos de nenhum outro crates para este projeto, mas iremos no primeiro projeto no Capítulo 2, então usaremos esta seção de dependências.

Agora abra src/main.rs e dê uma olhada:

Nome do arquivo: src/main.rs

fn main() {
    println!("Hello, world!");
}

Cargo gerou um programa Hello, world! para você, assim como o que escrevemos na Listagem 1-1! Até agora, as diferenças entre nosso projeto anterior e o projeto que o Cargo gera é que o Cargo colocou o código no diretório src, e temos um arquivo de configuração Cargo.toml no diretório superior.

O Cargo espera que seus arquivos de origem residam no diretório src. O diretório do projeto de nível superior é apenas para arquivos README, informações de licença, arquivos de configuração e qualquer outra coisa não relacionada ao seu código. Usar o Cargo ajuda a organizar seus projetos. Há um lugar para tudo e tudo está em seu lugar.

Se você iniciou um projeto que não usa Cargo, como fizemos com o projeto Hello, world!, você pode convertê-lo em um projeto que use Cargo. Mova o código do projeto para o diretório src e crie um arquivo Cargo.toml apropriado.

Construindo e executando um projeto Cargo

Agora vamos ver o que é diferente quando construímos e executamos o programa “Hello, world!” com Cargo! A partir do seu diretório hello_cargo, construir o seu projeto, digitando o seguinte comando:

$ cargo build
    Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
        Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs

Este comando cria um arquivo executável em target/debug/hello_cargo (ou target\debug\hello_cargo.exe no Windows) em vez de em seu diretório atual. Você pode executar o executável com este comando:

$ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows
Hello, world!

Se tudo correr bem, Hello, world! deve ser imprisso no terminal. A execução de cargo build pela primeira vez também faz com que Cargo crie um novo arquivo no nível superior: Cargo.lock. Este arquivo controla as versões exatas das dependências em seu projeto. Este projeto não tem dependências, então o arquivo é um pouco esparso. Você nunca precisará alterar este arquivo manualmente; Cargo gerencia seu conteúdo para você.

Acabamos de construir um projeto cargo build e executá-lo ./target/debug/hello_cargo, mas também podemos usar cargo run para compilar o código e, em seguida, executar o executável resultante em um único comando:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/hello_cargo`
Hello, world!

Observe que, desta vez, não vimos uma saída indicando que o Cargo estava compilando hello_cargo. Cargo descobriu que os arquivos não haviam mudado, então apenas executou o binário. Se você tivesse modificado seu código-fonte, o Cargo teria reconstruído o projeto antes de executá-lo e você teria visto esta saída:

$ cargo run
Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
 Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs
  Running `target/debug/hello_cargo`
Hello, world!

Cargo também fornece um comando chamado cargo check. Este comando verifica rapidamente seu código para garantir que ele compila, mas não produz um executável:

$ cargo check
 Checking hello_cargo v0.1.0 (file:///projects/hello_cargo)
  Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs

Por que você não quer um executável? Frequentemente, cargo check é muito mais rápido do que cargo build, porque pula a etapa de produção de um executável. Se você está verificando continuamente o seu trabalho enquanto escreve o código, usar cargo check irá acelerar o processo! Como tal, muitos Rustáceos executam cargo check periodicamente enquanto escrevem seu programa para garantir que ele seja compilado. Em seguida, eles executam cargo build quando estão prontos para usar o executável.

Vamos recapitular o que aprendemos até agora sobre o Cargo:

  • Podemos construir um projeto usando cargo build.
  • Podemos construir e executar um projeto em uma etapa usando cargo run.
  • Podemos construir um projeto sem produzir um binário para verificar se há erros usando cargo check.
  • Em vez de salvar o resultado da construção no mesmo diretório do nosso código, o Cargo o armazena no diretório de destino/debug.

Uma vantagem adicional de usar o Cargo é que os comandos são os mesmos, não importa em qual sistema operacional você está trabalhando. Portanto, neste ponto, não forneceremos mais instruções específicas para Linux e macOS versus Windows.

Construindo para Lançamento

Quando seu projeto estiver finalmente pronto para lançamento, você pode usar cargo build --release para compilá-lo com otimizações. Este comando criará um executável em target/release em vez de target/debug. As otimizações tornam o código do Rust mais rápido, mas ativá-las prolonga o tempo de compilação do programa. É por isso que existem dois perfis diferentes: um para o desenvolvimento, quando você deseja reconstruir rápida e frequentemente, e outro para construir o programa final que você dará a um usuário que não será reconstruído repetidamente e que será executado tão rápido quanto possível. Se você estiver comparando o tempo de execução do seu código, certifique-se de executar cargo build --release e avaliar com o executável no destino/release.

Cargo como Convenção

Com projetos simples, o Cargo não fornece muito valor em relação ao rustc, mas provará seu valor à medida que seus programas se tornam mais complexos. Com projetos complexos compostos de vários crates, é muito mais fácil deixar o Cargo coordenar a construção.

Embora o projeto hello_cargo seja simples, agora ele usa muitas das ferramentas reais que você usará no resto de sua carreira no Rust. Na verdade, para trabalhar em qualquer projeto existente, você pode usar os seguintes comandos para verificar o código usando Git, mudar para o diretório desse projeto e compilar:

$ git clone someurl.com/someproject
$ cd someproject
$ cargo build

Para mais informações sobre o Cargo, verifique sua documentação.

Resumo

Você já começou bem sua jornada com Rust! Neste capítulo, você aprendeu como:

  • Instale a última versão estável do Rust usando rustup.
  • Atualize para uma versão mais recente do Rust.
  • Abrir a documentação instalada localmente.
  • Escrever e executar um programa "Hello, Wold!" usando rustc diretamente.
  • Criar e execute um novo projeto usando as convenções do Cargo.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

Introdução a linguagem de programação Rust

Observação: esta edição do livro é igual a The Rust Programming Language, disponível em formato impresso e e-book na No Starch Press.

Bem-vindo ao The Rust Programming Language, um livro introdutório sobre o Rust. A linguagem de programação Rust ajuda a escrever software mais rápido e confiável. Ergonomia de alto nível e controle de baixo nível estão freqüentemente em desacordo no projeto de linguagem de programação; Rust desafia esse conflito. Ao equilibrar uma capacidade técnica poderosa e uma ótima experiência de desenvolvedor, o Rust oferece a opção de controlar detalhes de baixo nível (como o uso de memória) sem todo o incômodo tradicionalmente associado a esse controle.

Para quem é a linguagem de programação Rust

Rust é ideal para muitas pessoas por diversos motivos. Vejamos alguns dos grupos mais importantes.

Equipes de desenvolvedores

Rust está provando ser uma ferramenta produtiva para colaboração entre grandes equipes de desenvolvedores com vários níveis de conhecimento de programação de sistemas. O código de baixo nível está sujeito a uma variedade de bugs sutis, que na maioria das outras linguagens podem ser detectados apenas por meio de testes extensivos e revisão cuidadosa do código por desenvolvedores experientes. No Rust, o compilador desempenha um papel de guardião, recusando-se a compilar o código com esses bugs elusivos, incluindo bugs de simultaneidade. Trabalhando junto com o compilador, a equipe pode gastar seu tempo focando na lógica do programa ao invés de perseguir bugs.

Rust também traz ferramentas de desenvolvedor contemporâneas para o mundo da programação de sistemas:

  • Cargo, o gerenciador de dependências incluído e a ferramenta de construção, torna a adição, compilação e gerenciamento de dependências fácil e consistente em todo o ecossistema Rust.
  • Rustfmt garante um estilo de codificação consistente entre os desenvolvedores.
  • O Rust Language Server capacita a integração com as IDEs (Integrated Development Environment) para conclusão de código e mensagens de erro em linha.

Ao usar essas e outras ferramentas no ecossistema Rust, os desenvolvedores podem ser produtivos ao escrever código em nível de sistema.

Alunos

Rust é para estudantes e interessados ​​em aprender sobre conceitos de sistemas. Usando o Rust, muitas pessoas aprenderam sobre tópicos como desenvolvimento de sistemas operacionais. A comunidade é muito acolhedora e fica feliz em responder às perguntas dos alunos. Por meio de esforços como este livro, as equipes de Rust desejam tornar os conceitos de sistema mais acessíveis a mais pessoas, especialmente as novas em programação.

Empresas

Centenas de empresas, grandes e pequenas, usam Rust na produção para uma variedade de tarefas. Essas tarefas incluem ferramentas de linha de comando, serviços da web, ferramentas DevOps, dispositivos incorporados, análise e transcodificação de áudio e vídeo, criptomoedas, bioinformática, mecanismos de pesquisa, aplicativos da Internet das coisas, aprendizado de máquina e até mesmo partes importantes do navegador Firefox.

Desenvolvedores de código aberto

Rust é para pessoas que desejam construir a linguagem de programação, comunidade, ferramentas de desenvolvedor e bibliotecas Rust. Adoraríamos que você contribuísse com a linguagem Rust.

Pessoas que valorizam velocidade e estabilidade

Rust é para pessoas que desejam velocidade e estabilidade em um idioma. Por velocidade, queremos dizer a velocidade dos programas que você pode criar com o Rust e a velocidade com que o Rust permite que você os escreva. As verificações do compilador Rust garantem a estabilidade por meio de adições de recursos e refatoração. Isso contrasta com o código legado frágil em linguagens sem essas verificações, que os desenvolvedores geralmente têm medo de modificar. Buscando abstrações de custo zero, recursos de nível superior que compilam para código de nível inferior tão rápido quanto o código escrito manualmente, Rust se empenha em fazer com que o código seguro seja um código rápido também.

A linguagem Rust espera oferecer suporte a muitos outros usuários também; aqueles mencionados aqui são apenas alguns dos maiores interessados. No geral, a maior ambição de Rust é eliminar os trade-offs que os programadores aceitaram por décadas, fornecendo segurança e produtividade, velocidade e ergonomia. Experimente o Rust e veja se as escolhas funcionam para você.

Para quem é este livro

Este livro pressupõe que você escreveu código em outra linguagem de programação, mas não faz suposições sobre qual. Tentamos tornar o material amplamente acessível para aqueles com uma ampla variedade de experiências de programação. Não gastamos muito tempo falando sobre o que é programação ou como pensar sobre isso. Se você é totalmente novo em programação, seria melhor ler um livro que fornece especificamente uma introdução à programação.

Como usar este livro

Em geral, este livro presume que você o está lendo em sequência, do início ao fim. Os capítulos posteriores baseiam-se nos conceitos dos capítulos anteriores, e os capítulos anteriores podem não se aprofundar nos detalhes de um tópico; normalmente revisitamos o tópico em um capítulo posterior.

Você encontrará dois tipos de capítulos neste livro: capítulos de conceitos e capítulos de projetos. Nos capítulos conceituais, você aprenderá sobre um aspecto do Rust. Nos capítulos do projeto, construiremos pequenos programas juntos, aplicando o que você aprendeu até agora. Os capítulos 2, 12 e 20 são capítulos de projeto; o resto são capítulos de conceitos.

O Capítulo 1 explica como instalar o Rust, como escrever um programa Hello, world! e como usar o Cargo, o gerenciador de pacotes e ferramenta de compilação do Rust. O Capítulo 2 é uma introdução prática à linguagem Rust. Aqui, cobrimos os conceitos em alto nível e os capítulos posteriores fornecerão detalhes adicionais. Se você quiser sujar as mãos imediatamente, o Capítulo 2 é o lugar para isso. A princípio, você pode até querer pular o Capítulo 3, que cobre os recursos do Rust semelhantes aos de outras linguagens de programação, e ir direto para o Capítulo 4 para aprender sobre o sistema de propriedade do Rust. No entanto, se você for um aluno particularmente meticuloso que prefere aprender todos os detalhes antes de passar para o próximo, você pode querer pular o Capítulo 2 e ir direto para o Capítulo 3, voltando ao Capítulo 2 quando quiser trabalhar em um projeto aplicando os detalhes que você aprendeu.

O Capítulo 5 discute estruturas e métodos, e o Capítulo 6 cobre enums, expressões match e a construção de fluxo de controle if let. Você usará structs e enums para criar tipos personalizados em Rust.

No Capítulo 7, você aprenderá sobre o sistema de módulo de Rust e sobre as regras de privacidade para organizar seu código e sua API (Interface de Programação de Aplicativo) pública. O Capítulo 8 discute algumas estruturas de coleta de dados comuns que a biblioteca padrão fornece, como vetores, strings e mapas hash. O Capítulo 9 explora a filosofia e as técnicas de tratamento de erros de Rust.

O Capítulo 10 investiga genéricos, características e tempos de vida, que lhe dão o poder de definir o código que se aplica a vários tipos. O Capítulo 11 trata de testes, que mesmo com as garantias de segurança de Rust são necessários para garantir que a lógica de seu programa está correta. No Capítulo 12, construiremos nossa própria implementação de um subconjunto de funcionalidades da ferramenta de linha de comando grep que pesquisa texto em arquivos. Para isso, usaremos muitos dos conceitos que discutimos nos capítulos anteriores.

O Capítulo 13 explora fechamentos e iteradores: recursos do Rust que vêm de linguagens de programação funcionais.

No Capítulo 14, examinaremos o Cargo com mais detalhes e falaremos sobre as melhores práticas para compartilhar suas bibliotecas com outras pessoas.

O Capítulo 15 discute ponteiros inteligentes que a biblioteca padrão fornece e as características que permitem sua funcionalidade.

No Capítulo 16, examinaremos diferentes modelos de programação simultânea e falaremos sobre como o Rust o ajuda a programar em vários threads sem medo. O Capítulo 17 examina como os idiomas do Rust se comparam aos princípios de programação orientada a objetos com os quais você pode estar familiarizado.

O Capítulo 18 é uma referência sobre padrões e correspondência de padrões, que são maneiras poderosas de expressar ideias em programas Rust. O Capítulo 19 contém uma miscelânea de tópicos avançados de interesse, incluindo Rust inseguro, macros e muito mais sobre vidas, características, tipos, funções e encerramentos.

No Capítulo 20, concluiremos um projeto no qual implementaremos um servidor da web multithread de baixo nível!

Finalmente, alguns apêndices contêm informações úteis sobre a linguagem Rust em um formato mais parecido com uma referência. O Apêndice A cobre as palavras-chave do Rust, o Apêndice B cobre os operadores e símbolos do Rust, o Apêndice C cobre as características deriváveis ​​fornecidas pela biblioteca padrão, o Apêndice D cobre algumas ferramentas de desenvolvimento úteis e o Apêndice E explica as edições do Rust.

Não há maneira errada de ler este livro: se você quiser pular, vá em frente! Você pode ter que voltar aos capítulos anteriores se sentir alguma confusão. Mas faça o que funcionar para você.

Uma parte importante do processo de aprendizagem do Rust é aprender a ler as mensagens de erro que o compilador exibe: elas o guiarão para o código de trabalho. Como tal, forneceremos muitos exemplos que não compilam junto com a mensagem de erro que o compilador mostrará a você em cada situação. Saiba que se você inserir e executar um exemplo aleatório, ele pode não ser compilado! Certifique-se de ler o texto ao redor para ver se o exemplo que você está tentando executar está destinado a erro. Ferris também o ajudará a distinguir o código que não foi feito para funcionar:

Ferris Significado
Esse código não compila Este código não compila!
Este código entra em pânico Este código entra em pânico!
Este bloco de código contém código não seguro Este bloco de código contém código não seguro.
Este código não produz o comportamento desejado Este código não produz o comportamento desejado.

Na maioria das situações, levaremos você à versão correta de qualquer código que não compila.

Código fonte

Os arquivos de origem a partir dos quais este livro é gerado podem ser encontrados no GitHub.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

Prefácio

Nem sempre foi tão claro, mas a linguagem de programação Rust é fundamentalmente sobre empoderamento : não importa que tipo de código você está escrevendo agora, Rust permite que você vá mais longe, para programar com confiança em uma variedade maior de domínios do que você fazia antes.

Considere, por exemplo, o trabalho de “nível de sistema” que lida com detalhes de baixo nível de gerenciamento de memória, representação de dados e simultaneidade. Tradicionalmente, esse reino da programação é visto como misterioso, acessível apenas a alguns poucos selecionados que devotaram os anos necessários para aprender a evitar suas armadilhas infames. E mesmo aqueles que o praticam o fazem com cautela, para que seu código não fique aberto a exploits, travamentos ou corrupção.

Rust quebra essas barreiras eliminando as velhas armadilhas e fornecendo um conjunto de ferramentas amigável e polido para ajudá-lo ao longo do caminho. Os programadores que precisam “mergulhar” no controle de nível inferior podem fazer isso com Rust, sem assumir o risco habitual de travamentos ou falhas de segurança e sem ter que aprender os detalhes de uma cadeia de ferramentas inconstante. Melhor ainda, a linguagem foi projetada para guiá-lo naturalmente em direção a um código confiável e eficiente em termos de velocidade e uso de memória.

Os programadores que já estão trabalhando com código de baixo nível podem usar Rust para aumentar suas ambições. Por exemplo, a introdução do paralelismo no Rust é uma operação de risco relativamente baixo: o compilador detectará os erros clássicos para você. E você pode lidar com otimizações mais agressivas em seu código com a confiança de que não introduzirá acidentalmente travamentos ou vulnerabilidades.

Mas Rust não está limitado à programação de sistemas de baixo nível. É expressivo e ergonômico o suficiente para tornar aplicativos CLI, servidores da web e muitos outros tipos de código bastante agradáveis ​​de escrever - você encontrará exemplos simples de ambos posteriormente neste livro. Trabalhar com Rust permite que você desenvolva habilidades que são transferidas de um domínio para outro; você pode aprender Rust escrevendo um aplicativo da web e, em seguida, aplicar essas mesmas habilidades para direcionar seu Raspberry Pi.

Este livro abrange totalmente o potencial do Rust para capacitar seus usuários. É um texto amigável e acessível que visa ajudá-lo a evoluir não apenas em seu conhecimento sobre Rust, mas também em seu alcance e confiança como programador em geral. Então mergulhe, prepare-se para aprender e bem-vindo à comunidade Rust!

- Nicholas Matsakis e Aaron Turon

Tradução feita por Acervo Lima. O original pode ser acessado aqui.

Licença