# Workshop de Rust ## Introdução Leitura interessante: https://heartbleed.com/ ## Setup e impressões iniciais Instalação: https://rustup.rs/ Só rodar o comando abaixo no shell: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` > OBS: O comando acima vai instalar a versão `stable` por padrão. Para Windows (sem WSL), pode também optar por instalar pelo `chocolatey`. Ou em último caso em um `.exe` que pode ser baixado do site e instalado. Para verificar a versão: ``` rustc --version ``` ### Deu erro, e agora? Se o erro foi: ``` note: please ensure that VS 2013, VS 2015, VS 2017 or VS 2019 was installed with the Visual C++ option ``` Instalar o Visual Studio Build Tools. Se continuar: >Se deu ruim pra alguem no windows, tenta colocar RUSTFLAGS na env do user, com o parametro "-C link-self-contained=yes" --- Em distribuições baseadas em Debian, como Ubuntu, certificar que temos o `build-essentials` instalado: ``` sudo apt install build-essentials ``` ### O que acabamos de instalar? https://rust-lang.github.io/rustup/concepts/components.html Instalado por padrão: - `rustc` - Compilador - `clippy` - Linter, é a ferramenta que avalia a qualidade do código e te entrega warnings que não impedem a compilação. - `rust-docs` - Docs da lang - `rust-std` - Biblioteca padrão - `cargo` - Gerenciador de pacotes - `rustfmt` - Formatador Pode ser instalado depois, para oferecer suporte a editores de texto. - `rls` - Rust Language Server ### Iniciando um projeto em Rust 1. Fazer um `cargo new meu-projeto` 2. `cd meu-projeto` 3. `cargo run` para compilar e rodar! ### Rodando no VSCode Importante instalar essas extenões se quiser ter as features da linguagem no editor: - https://marketplace.visualstudio.com/items?itemName=rust-lang.rust - https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb ## Mão na massa Nosso tour está disponível [aqui](https://cuchi.github.io/tour_of_rust/00_pt-br.html). ### Tipos primitivos: ``` // Tipos primitivos // C -> Rust // char -> i8 // short -> i16 // int -> i32 // unsigned char -> u8 // unsigned short -> u16 // unsigned int -> u32 // unsigned long -> u64 // float -> f32 // double -> f64 // -> bool ``` ### O `println!` É um macro, normalmente usamos assim: ```rust println!("{:?}", valor); // Vai mostrar o valor "como ele é" na linguagem, útil para debugar. println!("{}", valor); // Vai mostar o valor da maneira "bonita". ``` ### Tipo `unit`, ou `()` ``` // Tipo "unit", equivalente ao "void" de C e C++ let x = (); ``` ### Ponteiros ```rust fn main() { let mut x = 42; muda_pra_10(&mut x); println!("{}", x); } fn muda_pra_10(x: &mut i32) { *x = 10 } ``` ### Problemas Esse daqui (usar o material até o capítulo 2): https://projecteuler.net/problem=1 #### Resposta ```rust // If we list all the natural numbers below 10 that are multiples of 3 or 5, // we get 3, 5, 6 and 9. The sum of these multiples is 23. // Find the sum of all the multiples of 3 or 5 below 1000. fn main() { println!("Soma imperativa {}", soma_imperativa()); println!("Soma funcional {}", soma_funcional()); } fn soma_imperativa() -> i32 { let mut sum = 0; for num in 1..1000 { if pode_somar(&num) { sum += num; } } return sum; } fn soma_funcional() -> i32 { (1..1000).filter(pode_somar).fold(0, |a, b| a + b) } fn pode_somar(num: &i32) -> bool { return num % 3 == 0 || num % 5 == 0; } ``` ### Bibliotecas em Rust Podemos também criar bibliotecas usando o `cargo new <nome> --lib`, o Rust tem o https://crates.io/ como repositório de pacotes onde podemos publicar e usar bibliotecas nos nossos projetos. ### Projeto Vamos fazer uma aplicação CLI. - Vai usar código orientado a objetos - Vai usar controle de erros - Vai ler do stdin e usar argumentos de linha de comando Vamos chamar ela de **tasks**: ``` ./tasks new Estudar rust ./tasks list ./tasks done 3 ./tasks delete 3 ``` #### String primitiva vs não-primitiva Temos 2 tipos de string: - `String` - Struct que implementa uma String, na realidade é um vetor mutável por dentro (vamos ver isso depois ;). Preferimos usar essa quando estamos trabalhando com structs e código orientado a objetos. - `&str` - String primitiva, não é mutável em nenhum caso #### Traits Caímos nesse problema: ``` `Task` doesn't implement `std::fmt::Display` ``` Ao fazer isso: ```rust println!("{}", task); ``` Como resolvemos isso? Investigando mais a fundo, vimos que `std::fmt::Display` é uma **Trait**: https://doc.rust-lang.org/std/fmt/trait.Display.html ```rust impl Display for Task { fn fmt(&self, formatter: &mut Formatter) -> Result { write!(formatter, "[{}]: done: {}", self.name, self.done) } } ``` O código acima vai fazer com que a `task` seja exibida pelo `println!("{}", task)` O mesmo de cima também é verdade para: ```rust println!("{:?}", task); ``` Mas nesse caso, vai ser a trait de `Debug`. Nesse caso, conseguimos usar um macro: ```rust #[derive(Debug)] struct Task { name: String, done: bool, } ``` Que vai gerar a implementação dessa **Trait** automaticamente. #### Macro `vec!` ```rust let tasks_vec = vec![ Task{name: String::from("Estudar rust"), done: false}, Task{name: String::from("Dormir"), done: true}, ]; // Equivalente a: // let mut tasks_vec = Vec::new(); // tasks_vec.push(Task{name: String::from("Estudar rust"), done: false}); // tasks_vec.push(Task{name: String::from("Dormir"), done: true}); ``` #### Controle de erros ```rust fn load(file_path: String) -> Result<TaskRepository, io::Error> { let result = fs::read_to_string(file_path)?; // ^ Se falhar, vai retornar um // Err(io:Error(...)) ``` ### Código do projeto ```rust use std::fmt; use std::fmt::{Debug, Display, Formatter}; use std::fs; use std::io; #[derive(Debug)] struct CliError(String); impl From<io::Error> for CliError { fn from(_: io::Error) -> CliError { CliError(String::from("Erro de I/O :(")) } } #[derive(Debug)] enum TaskStatus { Done, NotStarted, InProgress, } #[derive(Debug)] struct Task { name: String, status: TaskStatus, } #[derive(Debug)] struct TaskRepository { tasks: Vec<Task>, } impl Display for TaskStatus { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { write!( formatter, "{}", match self { TaskStatus::Done => "tá pronto!", TaskStatus::InProgress => "tô fazendo", TaskStatus::NotStarted => "nem comecei ainda", } ) } } impl Display for Task { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { write!(formatter, "[{}]: status: {}", self.name, self.status) } } impl Task { fn parse_status(status: &str) -> Result<TaskStatus, CliError> { match status { "done" => Ok(TaskStatus::Done), "in_progress" => Ok(TaskStatus::InProgress), "not_started" => Ok(TaskStatus::NotStarted), _ => Err(CliError(String::from(format!( "status {} não existe!", status )))), } } } impl TaskRepository { fn load(file_path: String) -> Result<TaskRepository, CliError> { let result = fs::read_to_string(file_path)?; let mut tasks: Vec<Task> = Vec::new(); let lines = result.split("\n"); for line in lines { let columns: Vec<&str> = line.split(" | ").collect(); if columns.len() != 2 { return Err(CliError(String::from(format!("Linha inválida:\n{}", line)))); } tasks.push(Task { status: Task::parse_status(columns[0])?, name: String::from(columns[1]), }) } Ok(TaskRepository { tasks: tasks }) } fn save(&self, _file_path: String) { // Vamos fingir que ele salva em um arquivo ;) } fn add_task(&mut self, name: String) { self.tasks.push(Task { name: name, status: TaskStatus::NotStarted, }) } fn length(&self) -> usize { self.tasks.len() } } fn main() -> Result<(), CliError> { let mut repository = TaskRepository::load(String::from("./tasks.txt"))?; println!("{:?}", repository); repository.add_task(String::from("Concluir esse projetinho :D")); // ... repository.save(String::from("./tasks.txt")); println!("{}", repository.length()); Ok(()) } ``` ### Próximos passos: Terminar o projetinho. Daqui em diante: https://cuchi.github.io/tour_of_rust/chapter_5_pt-br.html Fica a dica: https://rocket.rs/