A programação em Rust te intriga, porém você não tem ideia de onde começar?
Então, nesse caso, esse workshop é para você! Não importa se você é um iniciante ou já está familiarizado com o Rust - Todo o processo será escrito com o máximo de detalhes possíveis, e eu me certifiquei de incluir muitos recursos adicionais durante todo o workshop!
Nota: Este workshop foi elaborado com todos os tipos de níveis de habilidade em mente e pode levar de 30 minutos a uma hora e meia, dependendo do nível de familiaridade com Rust.
- Se você está familiarizado com programação, sinta-se à vontade para pular para a primeira parte da Parte 3.
- Se você também estiver familiarizado com o básico do Rust e com a criação de um novo projeto em Rust, pule para a Parte 4.
- Se você for trabalhar com outras pessoas neste workshop, certifique-se de que esteja familiarizado com o nível de habilidade delas. Esta oficina explica tudo o que está sendo feito com detalhes para acomodar a todos.
Demo
Você pode ver uma demonstração de um exemplo que preparamos no asciinema.org.
O código completo pode ser visto no GitHub e no repl.it.
Parte 1: Configuração 🔰
A fim de agilizar o processo, utilizaremos o repl.it.
Preparando seu projeto
Usar o repl.it é vantajoso no sentido de que você não precisa de nada além de um navegador para usá-lo. Você pode criar uma conta aqui e criar um novo projeto Rust indo para https://repl.it/languages/rust.
Depois de fazer isso, execute cargo init --name app-texto-rust na aba do Shell.
Parte 2: Estrutura de arquivos e o gerente de pacotes de carga 🏗
Seu projeto no Rust, no mínimo, deve ter este aspecto:
Cargo.toml
main.rs
Cargo é o gestor de pacotes que é usado entre a grande maioria dos desenvolvedores do Rust (também conhecida como "Rustaceanos"). É uma ferramenta conveniente que lida com muitas coisas, incluindo, mas não se limitando às seguintes:
-
Construir: O processo que usamos para descrever o processo onde o compilador (neste caso, o cargo) traduz o código que você pode escrever e ler para um formato que seu computador possa entender.
-
Instalar bibliotecas de software ("caixas") e dependências: O software depende de outras bibliotecas de software. O Cargo permite que você baixe bibliotecas de software que você encontrou na internet para seus projetos. Por exemplo, o Hack Clubber @anirudhb usou a biblioteca Serenity para interagir com o Discord de uma maneira muito mais fácil.
-
Distribuir seus programas: Compartilhar é cuidar! O Cargo também pode ajudá-lo a publicar seu código no crates.io junto com alguns detalhes adicionais que explicarei daqui a pouco. É particularmente útil em situações em que você deseja ser creditado por seu trabalho ou se você quiser usar versões. Todos estes detalhes estão incluídos no arquivo
Cargo.toml, que explicarei em pouco tempo.
Parte 2.1: O Manifesto (Cargo.toml)
O arquivo cargo.toml é o manifesto do seu projeto, que descreve seu projeto e suas dependências. Eis como seria um exemplo do arquivo Cargo.toml:
Cargo.toml:
[package]
name = "app-texto-rust"
version = "0.1.0"
authors = ["João Silva <joao.silva@exemplo.com>"]
edition = "2018"
namedefine como seu programa é chamado.versiondefine a versão de seu programa. É particularmente útil se você quiser emitir atualizações para as pessoas que estão utilizando seu programa!authorsé onde você coloca os nomes das pessoas que escreveram o programa.editiondescreve a versão de Rust que você quer que este projeto utilize. Você deve mantê-la como2018para ter certeza de utilizar os recursos mais recentes.
Se você quiser adicionar mais de um autor, você deve separar cada autor com uma vírgula e usar aspas:
authors = ["João Silva <joao.silva@exemplo.com>", "Maria Silva <maria.silva@exemplo.com>"]
Há também alguns items adicionais como tables e keys que você deve dar uma olhada, tais como license ou [[bin]]. Você pode ler mais sobre o manifesto aqui: https://doc.rust-lang.org/cargo/reference/manifest.html
Parte 3: Começando com o Rust 💫
Viva! Agora que a burocracia está fora do caminho, podemos finalmente chegar à parte divertida: Escrever código!
Para começar, vamos começar com algo simples para nos aconchegar escrevendo um programa "Olá, Mundo!".
Se você está familiarizado com o essencial da programação e do Rust, sinta-se à vontade para pular esta seção.
- Abra o arquivo
main.rs. Você verá o seguinte (ou algo semelhante):
fn main() {
println!("Hello World!");
}
Vamos fazer um rápido resumo do que está acontecendo aqui:
fn main()define uma nova "função" chamada main().
Esta é uma função muito importante! Quando os programas são executados, eles geralmente fazem o que a função main() lhes diz para fazer.
println!()é uma função diferente que se destina a "imprimir" informações na tela do jogador.
Essa operação é chamada de "imprimir" porque executar um programa nem sempre envolvia telas - ao invés disso, impressoras reais estavam envolvidas!
- Ponto-e-vírgula significa que a instrução termina aí. Eles estão lá para dar ordem e coerência ao nosso código.
A fim de entender a importância dos ponto-e-vírgula, farei com que o computador imprima duas coisas diferentes em uma linha:
main.rs:
fn main() {
println!("Olá, Mundo");
println!("Está um dia lindo lá fora!");
}
É possível perceber duas coisas a a partir daqui;
-
Os espaços não importam. (Mas eles fazem tudo parecer muito mais agradável, não é mesmo?)
-
Usar ponto-e-vírgula é como dividir as instruções que você está dando ao computador.
Agora, vamos executar o código que você escreveu. Abra um terminal na pasta em que seu projeto reside e execute os seguintes comandos:
cargo build
./target/debug/app-texto-rust
Se você tiver sorte o suficiente, agora você deve ver um pedaço de texto alegre dizendo Hello World!
Lembrete: Se você estiver usando o repl.it, você também pode simplesmente clicar no botão "Run" (Executar)!
Parte 4: Escrevendo o jogo ✍
Muito bem, nosso objetivo é escrever um programa que essencialmente narrará uma história ao jogador, dando-lhe instruções específicas. O jogador será capaz de afetar o curso da história com as decisões que ele entrará no programa.
Vamos começar!
Parte 4.1: Criando um pouco de texto
Neste momento, nosso código se parece com isso.
fn main() {
println!("Hello World!");
}
Porém, Hello World! não se parece como algo que se veria em um jogo, certo? Então, vamos mudar isso. Mude o texto dentro das citações para algo mais apropriado. Vamos mudá-lo para Olá corajoso explorador! Quer embarcar em uma aventura?.
Agora execute seu programa novamente. Em vez de Hello World!, coloque o que você escreveu acima.
Parte 4.2: Entrada do usuário
Um jogo precisa dar a possibilidade de interagir com ele e modificar a aventura.
Assim, vamos escrever uma nova função que aceitará ler a resposta do jogador e reagir de acordo.
Para simplificar, o jogador só deverá responder com uma palavra começando com "S", que inclui palavras como "Sim", "Si", "SimSim", ou "Sanduíche" (como uma consequência engraçada não intencional), da mesma forma com a letra "N". Para isso, vamos criar um novo arquivo com o nome de prompts.rs e vamos escrever o seguinte nele:
main.rs:
use std::io;
fn prompt() -> bool {
let mut entrada = String::new();
io::stdin().read_line(&mut entrada).unwrap();
return entrada.to_ascii_lowercase().starts_with("s");
}
fn main() {...}
Eu sei que isto é bastante para absorver, mas não se preocupe! Vamos passar pelo que está acontecendo aqui mais uma vez, linha por linha:
-
use std::io;A importação de bibliotecas nos permite utilizar código instalado pelo cargo, ou empacotado no Rust (biblioteca padrão). Ao importar
std::io(ondestdsignifica biblioteca padrão, também conhecida como Rust), ganhamos acesso a mais funções que nos permitem realizar operações de "entrada/saída" (comoprintln!(), mas mais avançadas!). -
fn prompt() -> bool {Todas as funções destinam-se a devolver alguma saída, que é visível para o jogador, para outros programas ou partes do mesmo programa. Neste caso, definimos uma nova função chamada
prompt(), que retornará um valor conhecido comobool, que significa "boolean" (booleano). Os tipos de dados booleanos só podem aceitar dois valores, que são denotados com as palavrastrue(verdadeiro) oufalse(falso) na linguagem de programação Rust. Eles recebem o nome do matemático George Boole e são possivelmente a instância mais simples da álgebra booliana no campo da ciência da computação. -
let mut entrada = String::new();Aqui, criamos um 'String' mutável. Mutabilidade define se você pode alterar o valor de uma variável. Já que estamos inicializando uma nova string (o que significa que declaramos que a variável
entradaexiste e é umaString, mas ainda não houve nenhum valor que tenha sido declarado a ela). -
io::stdin().read_line(&mut entrada).unwrap();Aqui, utilizando o módulo std
ionovamente, capturamos o que quer que o jogador esteja digitando até que ele aperteEnterdentro da variávelentrada. A&mut entradaque você vê é a sintaxe de Rust para criar uma referência mutável para uma variável. Neste caso, é assim que o métodoread_lineé capaz de ler a partir de seu buffer de entrada padrão; ele coloca os bytes na memória referenciada pela referência mutável que é passada. Neste caso, você está passando uma referência mutável paraentrada, então a linha resultante será colocada diretamente naquelaStringsem que nenhuma nova "string" seja criada. Note que tivemos que adicionar explicitamente o especificadormutà nossa referência; as referências e variáveis no Rust são instanciadas como imutáveis, a menos que especifiquemos o contrário. Em nosso caso, estamos especificando-a como mutável para que possa ser, bem, mutada pelo métodoread_line. -
return entrada.to_ascii_lowercase().starts_with("s");E finalmente, verificamos se a
entradacom todas as letras minúsculas começa coms. Se isso acontecer, significa que a entrada foi sim (ou similar), e retornatrue(verdadeiro). Se não, é seguro assumir que o jogador respondeu negativamente. Portanto, devolvemos o valorfalse(falso).
Parte 4.3: Verificando a resposta!
A fim de escrever uma história, faremos nosso programa reagir com base nas respostas que o jogador der. Em outras palavras, esta é a parte em que você escreve a lógica de seu próprio jogo! Dentro da função main, e em cima da macro println! (= uma função cujo nome termina com !), adicione o seguinte:
let resposta = prompt();
Agora você tem uma variável (imutável) chamada resposta, cujo tipo é um booleano ( verdadeiro ou falso). Agora podemos verificar a resposta do jogador e tomar um curso de ação dependendo da situação:
Vamos avaliar a resposta que o jogador nos deu!
// Se a resposta do jogador for sim, imprima a mensagem
if resposta == true {
println!("Êêê! Suas aspirações irão trazer coisas boas em sua vida.");
// caso contrário, se a respota for não (ou qualquer outra coisa!)
// imprima uma mensagem diferente, menos amigável
} else {
println!("Nãão, acho que deu ruim!");
}
Agora você pode criar um jogo adicionando, de forma semelhante, mais prompts, depois utilizando-os nos blocos if... else do prompt anterior e conectá-los.
Parte 4.4: Módulos do Rust
Uau... já são muitas linhas escritas. Se acrescentássemos somente mais uma, isso já ficaria muito confuso! Já que estamos prestes a escrever muitas linhas diferentes que aparecerão dependendo das escolhas que o jogador fizer, pode valer a pena dividir nosso jogo em arquivos diferentes, em vez de apenas colocar tudo no main.rs!
Então, vamos criar um novo arquivo chamado prompts.rs! A estrutura de nosso diretório deve se parecer com esta:
Cargo.toml
main.rs
prompts.rs
A fim de importar as funções que vamos escrever no prompts.rs, devemos acrescentar mod prompts na nossa primeira linha, para informar o compilador sobre a existência de nosso novo arquivo. Seu código deve agora estar assim:
main.rs:
use std::io;
mod prompts;
fn prompt() -> bool {
let mut entrada = String::new();
io::stdin().read_line(&mut entrada).unwrap();
return entrada.to_ascii_lowercase().starts_with("s");
}
fn main() {
let resposta = prompt();
// Se a resposta do jogador for sim, imprima a mensagem
if resposta == true {
println!("Êêê! Suas aspirações irão trazer coisas boas em sua vida.");
// caso contrário, se a respota for não (ou qualquer outra coisa!)
// imprima uma mensagem diferente, menos amigável
} else {
println!("Nãão, acho que deu ruim!");
}
}
Você pode ler mais sobre os módulos Rust aqui: https://doc.rust-lang.org/rust-by-example/mod.html
O Rust é instruído a executar a função chamada intro, que é exportada do prompts. Esta é uma função associada. Mas, atualmente, ela não existe. Vamos implementá-la agora?
Abra o arquivo prompts.rs, e cole o seguinte:
prompts.rs:
use std::io;
fn prompt() -> bool {
let mut entrada = String::new();
io::stdin().read_line(&mut entrada).unwrap();
return entrada.to_ascii_lowercase().starts_with("s");
}
pub fn intro() {
let resposta = prompt();
// Se a resposta do jogador for sim, imprima a mensagem
if resposta == true {
println!("Êêê! Suas aspirações irão trazer coisas boas em sua vida.");
// caso contrário, se a respota for não (ou qualquer outra coisa!)
// imprima uma mensagem diferente, menos amigável
} else {
println!("Nãão, acho que deu ruim!");
}
}
Você notará que o código é semelhante, mas ele tem algumas pequenas mudanças:
- Antes de mais nada, removemos
mod prompts;. Não queremos utilizar o arquivo com o qual estamos trabalhando. - Renomeamos a função
mainparaintro. Como a funçãomaintêm um nome especial para Rust, tivemos que mudar seu nome. - Também acrescentamos o modificador
pubà nossa função, o que nos permite chamar essa função de outros arquivos (que importamprompt.rs).
Vamos agora utilizar a função que escrevemos no main.rs. Substitua todo o código na função main.rs por apenas uma chamada para a função intro, como abaixo:
main.rs:
mod prompts;
fn main() {
prompts::intro();
}
Parte 4.4: Escrevendo um jogo completo
A fim de tornar o jogo mais satisfatório, divertido e cativante, é preciso haver mais diálogo!
main.rs:
mod prompts;
fn main() {
prompts::intro();
}
prompts.rs:
use std::io;
// retorna um bool, aceitando texto como entrada
fn prompt() -> bool {
let mut entrada = String::new();
io::stdin().read_line(&mut entrada).unwrap();
return entrada.to_ascii_lowercase().starts_with("s");
}
// primeiro prompt
pub fn intro() -> () {
println!("Alguém está te ligando, você atende?");
// se prompt() retorna true (verdadeiro), é suficiente para o rust continuar!
// algumas outras expressões que também são verdadeiras são 2 == 2,
// 1 > 0, ou apenas a palvara true.
if prompt() {
telefone_atendido();
} else {
println!("Você desligou o celular. Você não descobriu do que se tratava. Quem sabe um dia você descubra...");
}
}
// segundo prompt
pub fn telefone_atendido() -> () {
println! ("Você ouve uma voz do outro lado da linha: 'Já faz muito tempo, vamos se encontrar?'");
println!("Sua voz parece vagamente familiar, e ela soa um pouco angustiada. Você aceita?");
if prompt() {
desafio_aceito();
}
else {
println!("Você desligou o celular. Você não descobriu do que se tratava. Quem sabe um dia você descubra..");
println!("VOCÊ GANHOU! Continua...");
}
}
// terceiro prompt
pub fn desafio_aceito() -> () {
println!("Você se encontra com um velho amigo e ele lhe entrega uma bolsa cheia de dinheiro");
println!("Seu amigo diz: 'Pegue a bolsa, não faça perguntas.");
println!("Parabéns! Você agora é rico e já cobriu o suficiente suas necessidades materiais, mas será que isso representa a verdadeira felicidade? Você pode ter ganho o jogo, mas não ganhou na vida.");
println!("GAME OVER!");
}
O que está acontecendo aqui é bastante simples: Se o jogador atender seu telefone (telefone_atendido()), ele prossegue. Se não atender, ele perde. Depois de ouvir o que o chamador tem a dizer, ele perderá se aceitar a proposta do chamador (desafio_aceito()), mas ele ganhará se não o fizer.
Não apenas enganamos o jogador tomando as decisões mais óbvias, e depois jogando esta bomba nuclear moral na cara dele, mas também podemos preparar o terreno para uma sequência, caso eles decidam que receber muito dinheiro de graça é muito bom para ser verdade!
Além dos comentários no próprio código, há alguns detalhes importantes a serem observados:
- Neste exemplo, temos duas respostas possíveis, portanto, a história segue sempre dois caminhos totalmente diferentes. A maioria dos videogames acaba dando ao jogador a ilusão de escolha, fazendo-o passar por consequências de curto prazo e depois distorcendo a história de tal forma que tudo funciona da mesma maneira ou de uma maneira ligeiramente diferente, e eles acabam dando-lhe um final diferente. Títulos aclamados pela crítica, como The Last of Us, The Witcher, Night in the Woods, Mass Effect ou Assassin's Creed fazem isso.
- No entanto, ainda assim, acabamos dando ao jogador um diálogo diferente a cada vez. Em nosso caso, é que quase todas as decisões erradas expulsam o jogador do jogo.
- Você pode querer lidar com todas as cláusulas
if... elsee a lógica do jogo em uma função, em vez de lidar com a lógica por todo o lado. No entanto, acreditamos que a maneira como escrevemos deixa espaço para mais flexibilidade. - Também movemos todas as respostas corretas para sua própria função, para manter nosso código mais claro
Parabéns! Você acaba de escrever seu primeiro jogo em Rust. 🎉
Parte 5: Ideias! 💡
Aqui estão um monte de ideias que o ajudarão a dar um passo além:
- Você pode fazer um jogo de dados onde o jogador pode lançar um dado se eles entrarem com a palavra "lançar"?
- Você pode incluir mais opções em seu jogo para que o jogador possa escolher? (Dica: Booleanos aceitam apenas dois valores. Poderia retornar outro tipo de valor que lhe permita inserir mais respostas, e depois avaliar a resposta no próprio prompt?)
- Você pode fazer um jogo em que seu programa tenha um número aleatório, o jogador tenta adivinhá-lo e se ele acertar, o jogo parabeniza o jogador? (Dica: Em vez de um booleano, a função
prompt()deve retornar um número em vez disso) - Você pode fazer um jogo baseado na sorte, onde você escolhe tamanhos de dados e tem que lançar um número mais alto do que o computador?
- E se você pudesse construir jogos completos com gráficos?
Aqui estão algumas dicas (em inglês):
- The Rust Programming Language - Data Types
- The Rust Programming Language - Patterns and Matching
- Are we game yet?
Você também pode conferir este remix do código fonte deste workshop caso você fique preso, que implementa algumas das ideias apresentadas:
Parte 6: Leitura adicional 📖
- The Rust Programming Language - an in-depth guide to Rust. Este workshop foi inspirado no Capítulo 2 do livro
- Rust By Example - Para aqueles que preferem exemplos de código em vez de páginas de documentação
- Rust's learning resources page - incluindo guias para cursos avançados sobre outros tópicos
Agora que você terminou de construir este maravilhoso projeto, compartilhe sua bela criação com outras pessoas! Lembre-se, é só mandar a URL do seu projeto!


