# Rust básico (não é o jogo!)
Autores: Derick Martins, Lucas Heim e Nadine Schneider
## Introdução
Rust é uma linguagem de programação multiparadigma, compilada, desenvolvida pela Mozilla. A linguagem foca em ser segura, concorrente e rápida, e atinge esses objetivos com alguns elementos, como a ausência de um coletor de lixo, a concorrência sem disputa de dados e a abstração sem _overhead_.
Inicialmente, Rust era um projeto pessoal de um funcionário da Mozilla, Graydon Hoare, mas logo ela acabou ganhando suporte da empresa e cresceu, para hoje ser uma das 25 linguagens mais populares, conforme pesquisa da RedMonk. A principal motivação foi a construção de uma linguagem que diminuísse ao máximo a probabilidade de erros humanos, impedindo operações que podem gerar problemas.
Dentre as empresas que utilizam Rust, estão a própria Mozilla, com partes do navegador Firefox desenvolvidos nela, o Dropbox e a npm (Node Package Manager).
## Hello World
O programa a seguir possui a funcionalidade de printar a mensagem "Hello World" no console.
```rust=
//Isso é um comentário em linha, e não aparecerá na execução do programa.
/* Esse é um comentário com várias linhas, e também não aparecerá na execução do programa. */
/* A função main sempre irá ser executada quando o compilador binário for chamado */
fn main () {
println!("Hello World");
/*
A função println! na realidade não é uma função, mas sim um macro.
*/
}
```
## Operações básicas
Há outras formas de se printar "Hello World" utilizando o macro println!, visto na seção anterior:
```rust=
fn main() {
println!("{}, {}!", "Hello", "world"); // Hello, world!
println!("{greeting}, {name}!", greeting="Hello", name="world"); // Hello, world!
println!("{0}, {1}!", "Hello", "world"); // Hello, world!
}
```
É possível também printar vários argumentos sem previamente saber o tamanho que será recebido no macro:
```rust=
fn main (){
println!("{:?}", [9,1,5]); // [9, 1, 5]
}
```
Para separar cada elemento por linha:
```rust=
fn main () {
println!("{:#?}", [9,1,5]);
/* Saída:
[
9,
1,
5
]
*/
}
```
Como na maioria das linguagens, Rust também oferece suporte à operadores aritméticos:
```rust=
let numero = 5;
let soma = numero + 2; //7
let subtracao = numero - 3; //2
let multiplicacao = numero * 3; //15
let divisao = numero / 2; // 2 (pois ambos os numeros são do tipo integer)
let modulo = numero % 2; //1
let divisaoFloat = 5.0 / 2.0; //2.5 (pois ambos os numeros são do tipo float)
```
Ademais, possui operadores de comparação:
```rust=
let a = 1;
let b = 2;
let c = a != b; //true
let d = a == b; //false
let e = a > b; //false
let f = a < b; //true
let g = a <= a; //true
let h = a >= a; //true
let i = true > false; //true (por que? true, quando convertido para integer, tem o valor de 1, e false tem o valor de 0)
let j = 'a' > 'A'; //true (pois 'a' é maior que 'A' na tabela ASCII)
```
A linguagem Rust também disponibiliza a funcionalidade de achar o valor de algo dado um padrão. Isso é chamado de Pattern Matching. O comando usado para isso é o match. Por exemplo, é possível definir ações a partir de um valor encontrado num enumerador:
```rust=
//definicao do enumerador, que representa os meses do ano
enum Mes {
Janeiro, Fevereiro, Marco, Abril, Maio, Junho, Julho,
Agosto, Setembro, Outubro, Novembro, Dezembro
}
```
```rust=
match mes {
Mes::Dezembro | Month::Janeiro | Month::Fevereiro
=> println!(“É verao :|”),
Mes::Marco | Mes::Abril | Mes::Maio
=> println!(“É outono :)”),
Mes::Junho | Mes::Julho | Mes::Agosto
=> println!(“É inverno :D”),
Mes::Setembro | Mes::Outubro | Mes::Novembro
=> println!(“É primavera :)”),
}
```
No bloco acima, quando o mês faz parte da estação verão, é printado uma mensagem no console dizendo que é verão. O mesmo acontece para os outros meses e suas correspondentes estações.
## Variáveis e tipos de dados
Por padrão variáveis em Rust são imutáveis. Apesar disso, você ainda tem a opção de tornar as suas variáveis mutáveis.
Quando uma variável é imutável, assim que um valor passa a estar vinculado ao seu nome é impossível alterá-lo. Vejamos o exemplo a seguir:
```rust=
fn main() {
/* Aqui é atribuído o valor 5 à variável n e logo após o seu valor é impresso na tela*/
let n = 5;
println!("O valor de x é: {}", n);
/* Aqui está sendo atribuído o valor 6 à variável n, no entanto, neste ponto ocorrerá erro de compilação, pois, o Rust garante que a variável seja imutável */
n = 6;
}
```
No Rust, o compilador garante que quando afirmamos que um valor não mudará, ele realmente não mudará. Isso significa que, quando você está lendo e escrevendo código, não precisa acompanhar como e onde um valor pode mudar, o que pode tornar o código mais fácil de raciocinar.
No entanto, embora por padrão as variáveis sejam imutáveis isso não significa que não exista a possibilidade de alterá-las. Para isso, basta adicionar o *mut* na frente do nome da variável.
```rust=
fn main() {
/* Aqui é atribuído o valor 5 à variável mutável n e depois é impresso seu resultado em tela */
let mut n = 5;
println!("O valor de n: {}", n);
/* Neste ponto é atribuído o valor 6 à variável n, diferentemente do exemplo anterior, aqui não ocorrerá erro de compilação, pois através da cláusula mut definimos que n é uma variável mutável. */
n = 6;
println!("O valor de n: {}", n);
}
```
### Constantes
O conceito das variáveis por padrão não serem mutáveis pode ter lembrado as *constantes*, famosas em muitas linguagens de programação.
As constantes também são vinculadas a um tipo de dados e são imutáveis, mas existentes algumas diferenças com relação às simples variáveis:
1. Não é permitido utilizar mut com constantes;
2. Elas são sempre imutáveis;
3. As constantes são declaradas utilizando como palavra-chave o *const*;
4. Constantes podem somente ser definidas através de expressões e nunca através de resultados de chamadas de funções ou de qualquer outra forma que o valor seria obtido em tempo de execução;
Exemplo da declaração de uma constante, onde MAX_POINTS é seu nome e seu valor é 100.000.
```rust=
const MAX_POINTS: u32 = 100_000;
```
Por convenção da linguagem, constantes são declaradas tendo seu nome em letras maiúsculas e com um underline entre as plavras.
### Sombreamento
Podemos declarar novas variáveis com o mesmo nome de uma variável anterior, e a nova variável faz *sombra* à variável anterior. É dito então, que a primeira variável é sombreada pela segunda, o que significa que o valor da segunda variável é o que veremos quando usamos a variável. Podemos sombrear uma variável usando o mesmo nome da variável e repetindo o uso da palavra-chave *let* da seguinte forma:
```rust=
fn main() {
/* Inicialmente o valor 5 é atribuído a variável n */
let n = 5;
/* Em seguida, é feito sombreamento da variável n, utilizando o let, e é somado o valor n + 1. Tornando assim o valor igual a 6 */
let n = n + 1;
/* Neste último let é feito mais um sombreamento, pegando o valor de n e multiplicando por 2 */
let n = n * 2;
println!("O valor de n é: {}", n);
}
```
Se a plavra-chave *let* não for utilizada, ocorrerá erro de compilação, como já mencionado.
### Tipos de dados
O Rust é uma linguagem tipada estatisticamente , o que significa que ele deve conhecer os tipos de todas as variáveis em tempo de compilação. O compilador geralmente pode inferir o tipo que queremos usar com base no valor e em como o usamos.
O Rust possui quatro tipos escalares primários: inteiros, números de ponto flutuante, booleanos e caracteres. E possui dois tipos de compostos primitivos: tuplas e matrizes.
Veremos os principais tipos de dados.
#### Integer
Um inteiro é um número sem um componente fracionário.
Os tipos inteiros podem ser declarados utilizando um indicativo informando se são assinados ou não, por exemplo, i32 indica que o inteiro deve ser assinado e u32 indica que o inteiro não precisa ser assinado, neste caso, ambos utilizam 32 bits de espaço.
Um inteiro assinado *(i)* indica que aquele número deve ser sempre associado a um sinal e de maneira oposta inteiros não assinados *(u)* não precisam ser associados a nenhum sinal, sendo assim, por padrão são positivos. É como escrever números no papel: quando o sinal é importante, um número é mostrado com um sinal de mais ou um sinal de menos, no entanto, quando é seguro assumir que o número é positivo, ele é exibido sem sinal. Os números assinados são armazenados usando a representação do complemento de dois .
Os tamanhos de tipos de inteiros variam entre: 8, 16, 32, 64 e 128 bits. Podendo cada um deles ser acompanhado pelo i e u.
Tipos literais numéricos:
1. Decimal - 98_222
2. Hex - 0xff
3. Octal - 0o77
4. Binário - 0b1111_0000
5. Byte (u8 apenas) - b'A'
Quando não tiver certeza do qual tipo utilizar, os padrões do Rust geralmente são boas escolhas, e os tipos inteiros são padrão i32: esse tipo é geralmente o mais rápido.
Exemplos:
```rust=
fn main() {
let i = 5;
let d = 98_222;
let h = 0xff;
```
#### Tipos flutuantes
O Rust também possui dois tipos primitivos para números de ponto flutuante, que são números com pontos decimais. Os tipos de ponto flutuante do Rust são f32 e f64, que têm 32 bits e 64 bits, respectivamente.
Exemplo que mostra números de ponto flutuante:
```rust=
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
```
### Tipo booleano
Um tipo booleano no Rust possui dois valores possíveis: true e false. Os booleanos têm um byte de tamanho e em Rust é especificado usando bool. Por exemplo:
```rust=
fn main() {
let t = true;
let f: bool = false; //com anotação de tipo explícito
}
```
A principal maneira de usar valores booleanos é por meio de condicionais, como um if, por exemplo. Que será abordado mais adiante.
#### Char
O tipo char de Rust é o tipo alfabético mais primitivo da linguagem. Observe que os char literais são especificados com aspas simples, ao contrário dos literais de string, que usam aspas duplas. Exemplo do uso de char:
```rust=
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
}
```
#### Strings
A String é armazenado como um vetor de bytes, mas é garantido que sempre seja uma seqüência UTF-8 válida. A string é um heap alocado, expansível e não terminado em null.
O primeiro tipo de string é *&'static str*. É alocado estaticamente dentro do programa compilado.
```rust=
fn main() {
let hello = “Hello, this is Rust &‘static str”;
}
```
O segundo tipo é o tipo String dinâmico, que é alocado para heap.
```rust=
fn main() {
let s = “Hello, this will be String type”.to_string();
let s = String::from(“Hello, this will be String type”);
```
Criar uma nova string:
```rust=
fn main() {
let s = String::new();
}
```
Concatenação de strings:
```rust=
fn main() {
let str1 = "Hello ".to_string();
let str2 = "world!";
let concat = str1 + str2;
}
```
Substring:
```rust=
fn main() {
/* A segunda linha abaixo retorna os primeiros 5 bytes */
let hello_world = “Hello World”;
let hello = &hello_world[0..5];
println!("{}",hello);
}
```
Outros métodos;
```rust=
fn main() {
// push e pop char / string para e da string
s.push ('a');
s.push_str ("adicionado");
s.pop ();
// remove char na posição de byte
s.remove (3);
// comprimento da string em bytes
vamos strlen = s.len ();
// divide a string em dois
let (primeiro, último) = s.split_at (3);
}
```
#### Tupla
Uma tupla é uma maneira geral de agrupar alguns outros valores com uma variedade de tipos em um tipo composto. As tuplas têm um comprimento fixo: uma vez declaradas, elas não podem crescer ou diminuir de tamanho.
Criamos uma tupla escrevendo uma lista de valores separados por vírgulas entre parênteses. Cada posição na tupla tem um tipo e os tipos dos diferentes valores na tupla não precisam ser os mesmos. Exemplo:
```rust=
fn main() {
/* Variável tup representa a tupla */
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
```
É possível acessar diretamente um elemento da tupla a partir o período (.) seguido pelo índice do valor que se quer acessar. Conforme exemplo:
```rust=
fn main()
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
```
#### Matriz
Outra maneira de ter uma coleção de vários valores é com uma matriz . Ao contrário de uma tupla, todos os elementos de uma matriz devem ter o mesmo tipo. Os arrays em Rust tem tamanho fixo.
No Rust, os valores que entram em uma matriz são gravados como uma lista separada por vírgulas entre colchetes:
```rust=
fn main() {
let a = [1, 2, 3, 4, 5];
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
/* Aqui i32 define o tipo de cada elemento. Após o ponto e vírgula, o número 5 indica que o elemento contém cinco itens. */
let a: [i32; 5] = [1, 2, 3, 4, 5];
/* O array a aconterá 5 elementos que serão todos configurados para o valor 3 inicialmente. Isso é o mesmo que escrever, let a = [3, 3, 3, 3, 3]. */
let a = [3; 5];
}
```
Uma matriz é um único bloco de memória alocado na pilha. Você pode acessar elementos de um array usando indexação:
```rust=
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0]; //recebe o valor 1 que está no índice 0
let second = a[1];// recebe o valor 2 que está no índice 1
}
```
## Estrutura da linguagem
## Controle de fluxo e iterações
Uma parte importante de uma linguagem é a capacidade de lidar com os fluxos que compõem uma aplicação complexa.
### if/else
Rust utiliza condicionais de forma bem parecida com linguagens como Java e C++, utilizando blocos com chaves:
```rust
fn main() {
let n = 5;
if n < 0 {
print!("{} is negative", n);
} else if n > 0 {
print!("{} is positive", n);
} else {
print!("{} is zero", n);
}
}
```
Uma diferença é que o bloco _if_ pode retornar um valor, que pode ser atribuído à uma variável. Digamos que, se um número é pequeno, queremos aumentar ele em 1, mas se ele for grande, queremos diminuí-lo em um e atribuir isso à uma variável.
```rust=
fn main() {
let n = 5;
let x =
if n < 10 && n > -10 {
n + 1
} else {
n - 1
}; //Não esquecer do ponto-e-vírgula na atribuição!
println!("Novo número = {}", x);
//Resultado: Novo número = 6
}
```
### loop
A estrutura _loop_ simplesmente cria uma iteração infinita, como seria um `while(true)` de outras linguagens. Para encerrar, utiliza-se a palavra-chave `break`.
```rust=
fn main(){
let count = 1;
loop {
if count == 5 {
break;
}
print!("{}", count);
}
}
/*Resultado: 12345*/
```
É possível aninhar loops, porém a palavra-chave `break` sempre acaba parando todos os loops no qual ela está contida. Para evitar isso, é possível usar etiquetas, tanto na declaração do loop quando na hora de pará-lo.
```rust=
fn main() {
// Declara loop com o nome "fora"
'fora: loop {
println!("Entrou no loop de fora");
// Declara loop com nome "dentro"
'dentro: loop {
println!("Entrou no loop de dentro");
// Parando o loop de fora
break 'fora;
}
println!("Nunca vai chegar aqui");
}
println!("Saiu do loop de fora");
}
```
Finalmente, o loop ainda pode retornar um resultado:
```rust=
fn main() {
let mut x = 0;
let result = loop {
x += 1;
if x == 5 {
break x;
}
};
print!("{}", result);
// Resultado: 5
}
```
### while
O bloco _while_ possui uma sintaxe bastante comum, executando iterações até que uma certa condição seja atingida.
```rust=
fn main() {
let mut n = 0;
while n < 3 {
print!("{} ", n);
n += 1;
}
}
// Resultado: 0 1 2
```
### for in
Para trabalhar em cima de coleções, é possível utilizar a construção _for in_, que recebe uma lista pela qual fará a iteração. Para gerar uma lista de números, por exemplo, podemos usar um _range_, especificando um número inicial e um final entre dois pontos:
```rust=
fn main() {
for x in 1..5 {
print!("{}", x);
}
}
// Resulado: 1234
```
### match
A estrutura de _match_ se assemelha ao _switch_ de linguagens como C, porém apresenta uma sintaxe diferente.
```rust=
fn main() {
let x = 5;
match x {
// Um comportamento para cada valor de x
0 => println!("Zero"),
1 => println!("Um"),
2 | 3 => println!("Dois ou Três"),
4...10 => println!("De quatro a dez"),
_ => println!("Outro caso")
}
}
//Resultado: De quatro a dez
```
## Funções
Funções são declaradas utilizando a palavra-chave `fn`. Seus argumentos e retorno são tipados na declaração e o valor de retorno é a última expressão do bloco. Caso seja necessário sair da função antes do fim, pode ser usada a expressão `return`.
```rust=
fn main() {
let a = somaNumeros(3, 5);
println! ("Soma: {}", a); //Soma: 8
}
/*
i8 é o tipo integer de 8 bits; a parte '-> i8' é o que especifica o tipo do retorno
*/
fn somaNumeros(a: i8, b: i8) -> i8 {
a + b
}
```
Outra funcionalidade da linguagem, existente em outras linguagens modernas como o JavaScript, é a atribuição de função para uma variável, ou seja, tornar uma variável um ponteiro para uma função. Exemplo:
```rust=
fn main() {
let a = somaNumeros; //atribuição da função para variável
println! ("Soma: {}", a(3, 10)); //chamada da função com passagem de parâmetros
}
fn somaNumeros(a: i8, b: i8) -> i8 {
a + b
}
```
É possível também, na assinatura e passagem de parâmetros, se ter funções, não somente variáveis.
```rust=
fn main() {
let a = somaNumeros;
println! ("Soma duas vezes: {}", somaDuasVezesNumeros(a, 10, 5)); //Soma duas vezes: 30
}
fn somaNumeros(a: i8, b: i8) -> i8 {
a + b
}
fn somaDuasVezesNumeros (funcao: fn(i8, i8) -> i8, a: i8, b: i8) -> i8 {
//o primeiro parâmetro indica que está sendo esperado uma função que tenha dois integer de 8 bits como entrada, e uma saída também sendo um integer de 8 bits
funcao(funcao(a, b), funcao(a,b))
}
```
### Métodos
A relação objetos/métodos em Rust é representada com um elemento _struct_ como uma definição de classe e atributos e um elemento _impl_ que contém a implementação dos métodos.
```rust=
struct Retangulo {
altura: f64,
largura: f64
}
impl Retangulo {
fn area(&self) -> f64 {
self.altura * self.largura
}
}
fn main() {
let retangulo = Retangulo {
altura: 2.5,
largura: 2.5
};
print!("Área: {}", retangulo.area());
// Resultado: Área: 6.25
}
```
### Closures
Closures são definições de função anônimas, suportadas pela linguagem Rust, permitindo declarar execuções de funções, porém executá-las apenas no momento desejado. Essas funções possuem uma sintaxe diferente e podem capturar valores dos seus arredores. Esse tipo de função pode fazer inferência de tipos e então declarar operações com uma sintaxe menos verbosa.
```rust=
fn main() {
let x = 4;
// Nesse caso, o valor passado entre pipes é o parâmetro da função,
// É feita uma comparação com o valor de X, que não está declarado dentro da função,
// mas sim nos seus "arredores".
let igual_a_x = |z| z == x;
let y = 4;
println!("{}", igual_a_x(y));
}
```
### Funções de ordem superior
Rust, em seu caráter funcional, permite funções de ordem superior, que retornam outras funções ou recebem funções por parâmetro. Com isso, podemos utilizá-la de forma funcional para realizar diversas operações.
```rust=
fn main() {
let limite = 10;
let soma_dos_quadrados_pares: u32 =
(0..).map(|n| n * n) // Recebe uma sequência de números e coloca cada um ao quadrado
.take_while(|&n_squared| n_squared < limite) // Seleciona apenas até o limite
.filter(|&n_squared| n_squared % 2 == 1) // Filtra apenas os pares
.fold(0, |acc, n_squared| acc + n_squared); // Soma
println!("{}", soma_dos_quadrados_pares);
}
```
## Programação concorrente no Rust
Para entender como a linguagem Rust oferece e facilita o desenvolvimento da programação concorrente, é necessário entender o conceito desse paradigma.
Programação concorrente trata do cenário em que duas tarefas do sistema (thread) podem começar, rodar, e completar seu processamento numa mesma fatia de tempo. Isso não significa que elas irão rodar simultaneamente, nem que terminarão no mesmo tempo, mas que serão processadas pouco a pouco de forma que uma não esperará o término da outra pra começar o processamento.
Rust oferece suporte a isso através de sua biblioteca padrão (std).
### Spawn
O método spawn aceita um closure (explicado na seção anterior), e faz com que esse código seja executado numa nova thread:
```rust=
use std::thread;
fn main() {
thread::spawn(|| {
println!("Hello World de uma nova Thread!");
});
}
```
É possível passar valores de variáveis de uma thread para outra, através do closure move:
```rust=
use std::thread;
fn main() {
let x = 5;
thread::spawn(move || { //passando valor de x para a nova thread
println!("Valor de x: {}", x);
});
}
```
### Consistência de dados entre Threads
Um dos grandes problemas encontrados na programação concorrente, é a consistência de dados que estão compartilhados entre Threads. Para isso, a linguagem Rust fornece um conjunto de tipos para fazer o manuseio desses dados, garantindo a consistência entre eles. A linguagem por si só já oferece mecanismos de segurança, que previnem em tempo de compilação, a inconsistência de dados entre threads. Portanto, o código seguinte irá falhar logo no processo de compilação:
```rust=
use std::thread;
use std::time::Duration;
fn main() {
let mut data = vec![1, 2, 3];
for i in 0..3 {
thread::spawn(move || {
data[0] += i;
});
}
thread::sleep(Duration::from_millis(50));
}
```
O primeiro comando importante é o método clone, que retorna uma nova referência para um objeto. Entretanto, isso não garante a consistência, pois o valor original ainda pode ser alterado sem uma consistência entre as Threads. O seguinte código também irá falhar em compilação:
```rust=
use std::thread;
use std::time::Duration;
use std::rc::Rc;
fn main() {
let mut data = Rc::new(vec![1, 2, 3]);
for i in 0..3 {
// Create a new owned reference:
let data_ref = data.clone();
// Use it in a thread:
thread::spawn(move || {
data_ref[0] += i;
});
}
thread::sleep(Duration::from_millis(50));
}
```
O segundo passo importante, é a utilização do tipo Arc<T>, que garante a segurança de acesso por múltiplas Threads. Mas, isso também não é suficiente para garantir a consistência, pois o tipo Arc não permite a mutabilidade dos dados, apesar de permitir compartilhar. Portanto, o código irá falhar em sua compilação:
```rust=
use std::thread;
use std::sync::Arc;
use std::time::Duration;
fn main() {
let mut data = Arc::new(vec![1, 2, 3]);
for i in 0..3 {
let data = data.clone();
thread::spawn(move || {
data[0] += i;
});
}
thread::sleep(Duration::from_millis(50));
}
```
Por fim, para resolver o problema, o tipo de dados Mutex<T> contém uma abstração que garante que apenas uma thread por vez é capaz de mudar o valor de uma variável compartilhada.
O código final, permitindo uma concorrência segura entre as threads:
```rust=
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
for i in 0..3 {
let data = data.clone();
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[0] += i;
});
}
thread::sleep(Duration::from_millis(50));
}
```
De uma forma simples, entendendo os tipos que a linguagem oferece, é possível extrair um grande poder de processamento dos computadores, solucionando um dos grandes problemas enfrentados na programação atualmente que é a programação concorrente.
## Bibliotecas
### Reqwest
Essa biblioteca encapsula funções para comunicação pela web com o protocolo HTTP. A sua API é bastante simples e permite fazer requisições de forma pouco verbosa.
```rust=
let body = reqwest::get("https://www.rust-lang.org")?
.text()?;
println!("body = {:?}", body);
```
### Regex
Biblioteca para utilização de expressões regulares em Rust, que não são suportadas nativamente.
```rust=
use regex::Regex;
fn main() {
let re = Regex::new(r"(?x)
(?P<year>\d{4}) # the year
-
(?P<month>\d{2}) # the month
-
(?P<day>\d{2}) # the day
").unwrap();
let caps = re.captures("2010-03-14").unwrap();
assert_eq!("2010", &caps["year"]);
assert_eq!("03", &caps["month"]);
assert_eq!("14", &caps["day"]);
}
```
## Conclusão
Programar é difícil. Por mais que tenhamos um sistema completamente coberto por testes, sempre acabamos nos deparando com problemas que não estavam previstos, _edge_ cases, etc. Rust se tornou popular não só por possuir uma comunidade ativa e receptiva, mas por possuir um compilador extremamente "chato", que checa desde erros de sintaxe até gerenciamento de memória, evitando ao máximo problemas que acontecem apenas em tempo de execução.
Em um mundo em que basicamente tudo passa por sistemas, evitar erros é a chave para o sucesso e Rust ajuda muito nesse aspecto.
Se você deseja se aprofundar nessa linguagem, o site do Rust é a melhor porta de entrada: https://www.rust-lang.org/learn
## Referências
https://medium.com/learning-rust/rust-basics-e73304ab35c7
https://doc.rust-lang.org/stable/rust-by-example/
https://doc.rust-lang.org/std/string/struct.String.html
https://doc.rust-lang.org/1.30.0/book/first-edition/concurrency.html