Pular para o conteúdo

Módulos em Rust

Até agora, todos os exemplos ficaram em um único arquivo main.rs. Isso funciona bem para programas pequenos, mas projetos de verdade dividem o código em partes que fazem sentido juntas. A ferramenta do Rust para isso é o sistema de módulos: mod, pub e use.

Um módulo é um bloco com nome que agrupa código relacionado:

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

Saída:

5
▶ Experimente você mesmo

Repare em duas coisas. Primeiro, a função é chamada com math::add(...), usando o nome do módulo como caminho. Segundo, ela está marcada com pub. Sem isso, ninguém fora do módulo conseguiria chamá-la.

Vale destacar isto, porque costuma pegar as pessoas de surpresa: o conteúdo de um módulo é privado por padrão. Se você tirar o pub do exemplo acima, o código não compila:

mod math {
fn add(a: i32, b: i32) -> i32 { // sem `pub`
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ erro: function `add` is private
println!("{sum}");
}

O erro diz que a função add é privada. Tudo o que você quiser deixar disponível para quem está fora do módulo precisa ser marcado com pub, explicitamente. Isso é de propósito: assim o módulo pode esconder suas funções auxiliares internas e expor só as partes que fazem parte da sua API pública.

Módulos podem conter outros módulos. É assim que projetos maiores montam uma estrutura em forma de árvore:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ Experimente você mesmo

Escrever shapes::circle::area(...) toda vez cansa. O use traz um caminho para o escopo atual, e aí você pode usá-lo diretamente:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // mais curto que shapes::circle::area
println!("{area}");
}
▶ Experimente você mesmo

Na verdade, você já usou o use antes: é a mesma palavra-chave da página de HashMap (use std::collections::HashMap;).

Os exemplos acima colocam tudo em um só arquivo, porque o Rust Playground exige isso. Em um projeto de verdade, no seu computador, cada módulo costuma ganhar um arquivo próprio. Um projeto com um módulo math normalmente fica assim:

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // diz ao Rust: procure o código deste módulo em math.rs
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

mod math; (com ponto e vírgula e sem corpo) diz ao Rust para carregar o conteúdo do módulo a partir do arquivo math.rs, na mesma pasta. Você vai usar esse padrão assim que um projeto crescer além de um único arquivo.

Crie um módulo chamado convert com duas funções públicas: c_to_f (de Celsius para Fahrenheit) e f_to_c (o caminho inverso). No main, imprima 100°C são 212°F e 212°F são 100°C.

Dica: as fórmulas são c * 9.0 / 5.0 + 32.0 e (f - 32.0) * 5.0 / 9.0. Não esqueça o pub, e chame as funções com o nome do módulo, como convert::c_to_f(100.0).

Ver solução
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°C são {}°F", convert::c_to_f(100.0));
println!("212°F são {}°C", convert::f_to_c(212.0));
}

Saída:

100°C são 212°F
212°F são 100°C
▶ Experimente você mesmo

As duas funções são pub, então o main (que fica fora do módulo) pode chamá-las. Tire um dos pub e o compilador avisa que aquela função é privada.

Crie um módulo aninhado school::grades com uma função pública letter(score: u32) -> char, que devolve 'A' para 90 ou mais, 'B' para 80 ou mais e 'C' nos outros casos. Dentro de grades, escreva também uma função auxiliar privada is_at_least(score: u32, min: u32) -> bool, que letter usa. Traga school::grades para o escopo com use e imprima a letra (o conceito) de 95, 85 e 70.

Dica: tanto o mod grades de dentro de school quanto o fn letter precisam de pub, mas is_at_least não deve ter. O código dentro do mesmo módulo sempre pode chamar funções privadas.

Ver solução
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// privada: só o código dentro de `grades` pode chamar esta função
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

Saída:

95 -> A
85 -> B
70 -> C
▶ Experimente você mesmo

letter pode chamar is_at_least porque as duas ficam no mesmo módulo. No main, grades::is_at_least(95, 90) falharia com “function is_at_least is private” (a função is_at_least é privada). A ideia é exatamente essa: o módulo mostra uma API pública pequena e guarda as funções auxiliares para si.

  • mod nome { ... } agrupa código relacionado em um módulo com nome.
  • Tudo dentro de um módulo é privado por padrão. Coloque pub para deixar visível fora do módulo.
  • O use traz um caminho para o escopo, para você não precisar escrevê-lo inteiro toda vez.
  • Em um projeto com vários arquivos, mod nome; (sem corpo) carrega o código do módulo a partir do arquivo de mesmo nome.
  • O código dentro de um módulo sempre pode usar os itens privados desse módulo. Então deixe as funções auxiliares privadas e exponha só o que quem chama precisa.

Teste o que aprendeu

1. Por padrão, os itens dentro de um mod são públicos ou privados?

2. Como você diz ao Rust para carregar o código de um módulo a partir de math.rs, em vez de escrevê-lo ali mesmo?

Pontuação: 0 / 2