Olá, pessoal. Sejam muito bem-vindos à Alura! Sou o Vinicius Dias e guiarei vocês nesse treinamento de Rust, em que vamos conhecer um pouco mais sobre alguns tipos de Rust. Já falamos em um treinamento anterior sobre sintaxe básica, um pouco de Pattern Matching e até alguns tipos primitivos como inteiros, números de ponto flutuante (ou decimais), booleanos, caracteres etc. Agora, nesse treinamento, vamos evoluir e falar de tipos um pouco mais complexos.
Primeiro, vamos aprender sobre alguns conjuntos de dados, por exemplo, como alocar múltiplas variáveis de um mesmo tipo. Se quisermos armazenar notas de provas em uma mesma variável — ou seja, vários números decimais em uma única variável —, como fazemos? Para isso, aprenderemos sobre arrays.
Depois, vamos estudar como criar nossos próprios tipos. Se temos um tipo em que os valores são delimitados e já conhecidos, podemos utilizar uma estrutura chamada Enum
. Criaremos uma Enum
com dias da semana e outra com cores, e descobriremos como passar valores para essa Enum
sobre cores.
Nesse processo, vamos perceber que é possível passar valores para Enums
em mais de um formato, seja através de formatos de tupla (ou seja, somente informando os tipos) ou em formato de struct
, em que informamos o nome e o valor.
Em seguida, vamos falar sobre uma Enum
específica, a de Option
. No treinamento anterior, estudamos o Result
, e Option
é bastante parecida. Nesse curso, veremos o valor desse tipo de Enum
.
Entendido o Option
, vamos partir para um "tipo" de array, ou seja, um vector — uma estrutura chamada Vec
que é basicamente um array dinâmico, em que não precisamos saber o tamanho dele e podemos ir alocando conforme necessário. Nesse ponto, vamos até falar um pouco sobre performance e como tentar evitar realocações de memória.
Depois de aprender isso tudo, vamos ver como agrupar dados que faz sentido estarem juntos. Por exemplo, se quisermos representar uma conta-corrente com titular e saldo, como agrupamos esses valores de modo que fiquem juntos? Para isso, aprenderemos sobre o struct
. Vamos, inclusive, aprender a criar métodos para essa struct
.
Então, nesse treinamento, vamos estudar sobre como criar nossos tipos, alguns tipos de Rust, conjunto de dados etc. Eu sei que é bastante conteúdo, portanto se restar alguma dúvida nesse processo, não hesite: você pode abrir um tópico no fórum.
Tento responder pessoalmente sempre que possível. Quando não consigo, temos uma vasta comunidade de alunos, moderadores, instrutores e, com certeza, alguém vai conseguir te ajudar.
Em alguns momentos desse treinamento, farei referências a outras linguagens, principalmente C++ porque, além de ser compilada, já falei bastante sobre ela por aqui.
Se em algum momento essas referências não estiverem claras, não fizerem muito sentido, você pode aproveitar não só para usar o fórum e sanar suas dúvidas mas também dar uma olhada nos cursos que temos. Pelo menos para mim, ter estudado C++ ajudou bastante a entender o Rust.
Enfim, chega de falação, vamos ao próximo vídeo para colocar a mão na massa!
Bem-vindos de volta. Antes de começarmos a colocar a mão na massa, vamos só relembrar qual é o nosso ponto de partida. No final do último treinamento, criamos um projeto novo, vazio, utilizando o Cargo. É esse o projeto que vamos utilizar agora. Por enquanto, ele só tem uma função main
com um simples Hello World.
Também configuramos o Visual Studio Code ou, caso você utilize algum editor diferente, você viu alguns plugins para ele. Então, já conseguimos compilar e rodar o nosso código sem precisar abrir um terminal, compilar o arquivo e depois executá-lo. Dessa forma, com um atalho do teclado, já rodamos o programa.
Agora, vamos imaginar que estamos desenvolvendo um sistema acadêmico, por exemplo, e precisamos armazenar todas as notas de uma pessoa. No caso, uma pessoa tem quatro notas durante o ano, correspondentes aos quatro bimestres do ensino no Brasil.
Então, vamos supor que, para armazenar essas quatro notas, precisamos toda vez criar quatro variáveis diferentes. Criaríamos, por exemplo, uma variável nota1
e, como é um valor muito pequeno e não pode ser negativo, utilizaríamos o unsigned de 8 bits e ficaria let nota1: u8 = 10;
.
Então, digamos que a nota 1 foi 10 (let nota1: u8 = 10;
); a nota 2 foi 8 (let nota2: u8 = 8;
), a nota 3 foi 9.5... Já notamos que não podemos usar o unsigned, porque temos números flutuantes. Portanto, vamos mudar. É interessante usar o mesmo tipo em todas notas, então teremos que modificar todos para f32
manualmente! Não é muito conveniente:
fn main() {
let nota1: f32 = 10;
let nota2: f32 = 8;
let nota3: f32 = 9.5
}
Nas duas primeiras notas, como estou utilizando números inteiros e os atribuindo a variáveis do tipo float, o que preciso fazer? Temos algumas opções, a mais simples é adicionar o tipo depois dela:
fn main() {
let nota1: f32 = 10f32;
let nota2: f32 = 8f32;
let nota3: f32 = 9.5;
}
Quando adiciono o tipo depois do valor, é feito um cast, ou seja, armazenamos o valor do jeito que ele é, mas ocupando o espaço do outro tipo que especificamos (no caso, f32
), sem modificação — é isso que o cast faz. Dessa forma, podemos fazer cast de tipos em Rust.
Continuando, a nota 4 será 6 (let nota4: f32 = 6f32;
). Outra opção seria adicionar um ponto: let nota4: f32 = 6.0;
.
Assim, com o println!()
, posso exibir todas as notas. Vamos fazer a quebra de linha para não ficar ruim de ler, deixando cada parâmetro em uma linha:
fn main() {
let nota1: f32 = 10f32;
let nota2: f32 = 8f32;
let nota3: f32 = 9.5;
let nota4: f32 = 6.0;
println!(
"Nota1 = {}, Nota2 = {}, Nota3 = {}, Nota4 = {}",
nota1,
nota2,
nota3,
nota4
);
}
Vai ficar extenso, mas estará mais legível. Então, vamos executar, não esperamos nenhuma surpresa, serão exibidas todas as notas.
Com essa abordagem, temos vários problemas. Se quisermos percorrer todas essas notas, precisaremos manualmente selecionar cada uma delas — não conseguimos percorrer de forma dinâmica, por exemplo, utilizando um loop ou estrutura parecida.
Se precisarmos mudar o tipo delas, será necessário ir em cada uma das definições e alterá-las. Se não soubermos quantas notas temos definidas, precisaremos contar uma a uma — não há um modo automatizado de checar quantas variáveis começam com o nome "nota", por exemplo.
Então, do jeito que está, não é intuitivo. Para armazenarmos conjuntos, listas ou dados relacionados juntos, podemos utilizar um array.
Geralmente, traduzimos esse termo para "vetor", em português, por causa dos vetores matemáticos. Neste curso, tentaremos chamá-los sempre de arrays, porque é a terminologia utilizada no Rust e, mais adiante neste treinamento, perceberemos que há uma chance de confundirmos com uma outra estrutura.
Então, vamos criar um array, existem algumas formas diferentes de fazer isso. Vamos declarar uma variável notas
e informar as notas entre colchetes: let notas = [10f32, 8f32, 9.5, 6.0]
. Note que continuamos indicando que alguns valores são floats de 32 bits. Assim, utilizamos todas as notas em uma única variável.
Podemos apagar as outras variáveis nota1
, nota2
, nota3
e nota4
. Agora, no println!()
, em vez de chamar variáveis diferentes, vou utilizar notas
em todas elas. Porém notas
é uma lista de dados, é um array de dados, um conjunto. Como selecionamos só a primeira, a segunda, a terceira?
Assim como em várias ou praticamente todas as linguagens de programação, usaremos a notação notas[0]
. Começando do 0, selecionamos a primeira nota; no índice 1, escolhemos a segunda nota; no índice 2, a terceira, e assim em diante:
fn main() {
let notas = [10f32, 8f32, 9.5, 6.0];
println!(
"Nota1 = {}, Nota2 = {}, Nota3 = {}, Nota4 = {}",
notas[0],
notas[1],
notas[2],
notas[3]
);
}
Ao exibir, tenho o mesmo resultado. Além disso, também poderíamos utilizar um loop para percorrer.
Agora que já entendemos o que é o array e como o declaramos, vamos entender qual é a definição de tipo. Em [10f32, 8f32, 9.5, 6.0]
deixamos que o próprio Rust definisse qual é o tipo da variável notas
. Passando o mouse sobre notas
, veremos [f32; 4]
. Trata-se de um array de f32
— ou seja, as variáveis são do tipo float, utilizando 32 bits, porque foi o que especificamos — e temos 4 valores.
É importante ressaltar que, em um array, sempre sabemos o tamanho dele, sempre sabemos quantos itens o array pode ter, isto é, a sua capacidade. Se quisermos deixar essa indicação explícita, podemos declarar assim: let notas: [f32; 4] = [10f32, 8f32, 9.5, 6.0];
.
Por exemplo, se removermos essa informação e colocarmos let notas = [10.0, 8.0, 9.5, 6.0];
, quando passamos o mouse sobre notas
, será mostrado [f64; 4]
. O Rust interpretou que as variáveis são float, mas por garantia, por não saber o tamanho dos números que vamos ter, ele optou por um float de 64 bits.
De novo, se quisermos mudar os tipos, podemos modificar de uma vez só: let notas: [f32; 4] = [10f32, 8f32, 9.5, 6.0];
. Então, dessa forma, declaramos os valores de um array e também conseguimos declarar o tipo dele.
No próximo vídeo, vamos entender um pouco sobre como manipular esse array e o que podemos fazer com ele.
Bem-vindos de volta. Então, vamos ver algumas formas que podemos fazer para manipular esse array e brincar um pouco com ele. Primeiro, como posso percorrê-lo de forma dinâmica, em vez de acessar item por item?
Podemos remover todo o println!()
e vamos utilizar algum loop, por exemplo, o for
, feito justamente para percorrer elementos dentro de algum conjunto, algum iterador, como chamamos. Por padrão, arrays já possuem um iterador. Então isso vai ser bem fácil.
Posso fazer um for nota in notas
. Ou seja, quero pegar cada uma das notas dentro de notas
. E posso exibi-las com o println!()
:
fn main() {
let notas: [f32; 4] = [10.0, 8.0, 9.5, 6.0];
for nota in notas {
println!("A nota é = {}", nota);
}
}
Repare que, dentro do array de notas, com um simples for
consigo pegar cada um dos itens. Vou tentar executar e ver se não digitei nada errado. Está lá, a nota é 10, 8, 9.5, 6.
Outra forma de fazer isso, ainda com o for
, é pegar cada um dos índices, ou seja, contar de 0 até 3 que é o último índice. Ou seja, 0, 1, 2, 3 (os meus quatro itens) e pegar esse índice para podermos exibir, por exemplo, "A nota 1 é 10, a nota 2 é 8".
Então para fazermos isso, vou fazer um for indice in 0..4
, ou seja, vou percorrer o índice e não a nota em si, dentro de um intervalo de 0 até o tamanho desse array, que sabemos ser 4. Então, eu poderia fazer for indice in 0..4
, mas não estaria muito dinâmico.
Para buscar o tamanho de um array, fazemos notas.len()
. Dessa forma, tenho acesso ao tamanho de um array de forma dinâmica. "Vinicius, mas sempre sabemos o tamanho de um array?" Sim, só que notas.len()
me permite mais flexibilidade, se amanhã ou depois eu precisar mudar esse array, adicionar mais itens, consigo continuar usando esse for
.
Agora, posso utilizar println!("A nota {} é {}", nota)
para dizer, por exemplo, que a nota 1 é 10. Só que a minha variável nota
não existe mais. Então vamos adaptar.
Primeiro indice + 1
, porque o índice começa com 0, então no índice 0 temos a nota 1. Depois notas[indice]
, para eu acessar a nota efetivamente, acessando o meu vetor notas
no índice que peguei dentro desse for
:
fn main() {
let notas: [f32; 4] = [10.0, 8.0, 9.5, 6.0];
for indice in 0..notas.len() {
println!("A nota {} é = {}", indice + 1, notas[indice]);
}
}
Repare que é por isso que essa sintaxe exclui o último número, o intervalo de 0 a 4 vai pegar os índices 0, 1, 2 e 3 justamente para podermos pegar as estruturas de dados que começam com 0. Então aqui não precisaria fazer algo como 0.=notas.len() -1
ou colocar parênteses, dificultar. É bem mais simples. Ele simplesmente exclui o último número para não precisarmos subtrair do valor que normalmente já utilizamos.
Ao executar, temos "A nota 1 é 10, a nota 2 é 8" e assim em diante. Um outro detalhe é que, às vezes, quero criar um array e ele pode ser muito grande, por exemplo, quero ter 10 notas. Só que quero inicializar todas com 6.5. Para não ter que digitar 6.5 nas quatro posições, posso utilizar uma sintaxe interessante.
No valor, vou dizer que quero valor 6.5, só que quero esse valor quatro vezes. Quero um array de quatro elementos com o valor 6.5: let notas: [f32; 4] = [6.5; 4];
. Então repare que isso está do lado direito do operador de atribuição. Ou seja, [6.5; 4]
não é um tipo, é o valor, ele criar um array de quatro elementos, todos eles com o valor 6.5.
Ao executar, todas as minhas notas vão ser 6.5. Aqui já começamos a manipular e brincar um pouco com arrays. Agora, imagine que eu queira, por exemplo, ter arrays multidimensionais. Quero ter uma matriz, como normalmente traduzimos.
Então, vamos fazer uma função de matriz. Quero definir uma matriz, que vai ser um vetor de vetores, ou um array de arrays — let matriz = [[ ]]
:
fn main() {
let notas: [f32; 4] = [10.0, 8.0, 9.5, 6.0];
for indice in 0..notas.len() {
println!("A nota {} é = {}", indice + 1, notas[indice]);
}
matriz();
}
fn matriz() {
let matriz = [[]];
}
E vou ter, por exemplo, duas posições: [0.0, 1.2, 0.1]
e embaixo vou ter outros números, [1.3, 0.3, 1.4]
:
// código anterior omitido
fn matriz() {
let matriz = [
[0.0, 1.2, 0.1],
[1.3, 0.3, 1.4]
];
}
Então posso ter arrays de arrays e isso gera um array multidimensional ou, como falamos na matemática, uma matriz. Para percorrer essa matriz, posso utilizar o for
de novo. Para cada uma das linhas dessa minha matriz (for linha in matriz
), posso fazer um outro for
pegando um item em si, para cada item na minha linha (for item in linha
).
Repare que, para percorrer matrizes, preciso de um for
aninhado:
// código anterior omitido
fn matriz() {
let matriz = [
[0.0, 1.2, 0.1],
[1.3, 0.3, 1.4]
];
for linha in matriz {
for item in linha {
println!("Item = {}", item);
}
}
}
Assim, será exibido: 0.0, 1.2. 0.1, depois 1.3, 0.3 e 1.4. Simples assim. Então deixa eu executar. E beleza, "Item = 0", 1.2, 0.1 e assim em diante.
E caso você esteja se perguntando qual é o tipo desse dado, ele é um vetor de vetor, [[f64 ; 3]; 2]
. O vetor interior,[f64; 3]
, tem 3 itens porque é quanto cada uma de nossas linhas tem; e temos duas linhas. Então se eu quisesse definir isso de forma explícita, por exemplo, trocando o f64
pelo f32
para economizar um pouco de memória, posso fazer let matriz: [[f32; 3]; 2]
.
É um vetor de vetores e possui duas linhas. O tipo é f32
e cada um dos vetores tem três elementos. Repare que, sempre que trabalho com arrays, eu conheço o número de elementos.
Não consigo ter um array com valor dinâmico. Vamos conversar sobre esse assunto mais adiante. Mas já saiba que é bem importante deixarmos claro, bem definido.
Vamos executar de novo só para garantir que esse código não quebrou:
// código anterior omitido
fn matriz() {
let matriz: [[f32; 3]; 2] = [
[0.0, 1.2, 0.1],
[1.3, 0.3, 1.4]
];
for linha in matriz {
for item in linha {
println!("Item = {}", item);
}
}
}
Agora quero voltar lá para a parte do código em que temos o notas
e trazer um detalhe interessante para vocês.
Imagine que tenha um inteiro qualquer, por exemplo, let inteiro = 0;
e quero exibi-lo. Inclusive, vou informar que ele é um unsigned de 8 bits: let inteiro: u8 = 0;
. Para deixar mais padrão, vamos dizer assim, um signed de 64 bits, let inteiro: i64 = 0;
. Vou ocupar bastante memória.
No println!()
, vou fazer de um valor que vai ser notas
com esse inteiro como índice:
fn main() {
let notas: [f32; 4] = [10.0, 8.0, 9.5, 6.0];
let inteiro: i64 = 0;
println!("{}", notas[inteiro);
for indice in 0..notas.len() {
println!("A nota {} é = {}", indice + 1, notas[indice]);
}
}
// código posterior omitido
Então quero acessar o valor 0 das minhas notas. Repare que está alegando um erro. Quando executo, a mensagem não é amigável: "slice indices are of type usize
". Vamos entender um pouco melhor essa mensagem de erro no próximo vídeo.
O curso Rust: aprenda mais sobre tipos possui 99 minutos de vídeos, em um total de 45 atividades. Gostou? Conheça nossos outros cursos de em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.