English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Gerenciamento de Organização do Rust

Se um linguagem de programação não puder organizar o código, é difícil aprofundar-se nela, praticamente nenhum produto de software é composto por um único arquivo de origem.

Até agora, todos os programas deste tutorial foram escritos em um único arquivo, principalmente para facilitar o aprendizado da sintaxe e conceitos do idioma Rust.

Para um projeto, a organização do código é extremamente importante.

No Rust, há três conceitos organizacionais importantes: caixa, pacote, módulo.

Caixa (Crate)

"Caixa" é um arquivo de programa binário ou arquivo de biblioteca, que existe dentro do "pacote".

"Caixa" é uma estrutura em árvore, a raiz é o programa compilado dos arquivos de origem compilados quando o compilador começa a executar.

Nota: "arquivo de programa binário" não é necessariamente "arquivo executável binário", pode ser determinado que é um arquivo que contém o código da máquina-alvo, o formato do arquivo varia conforme o ambiente de compilação.

Pacote (Package)

Quando usamos o comando new do Cargo para criar um projeto Rust, um arquivo Cargo.toml será criado na pasta do projeto. A essência do projeto é um pacote, que deve ser gerenciado por um arquivo Cargo.toml, que descreve as informações básicas e dependências do pacote.

Um pacote pode conter no máximo um "caixa" de biblioteca, pode conter qualquer quantidade de "caixas" binárias, mas pelo menos uma "caixa" (seja biblioteca ou "caixa" binária).

Após a criação do pacote com o comando cargo new, um arquivo de origem main.rs será gerado na pasta src, o Cargo define esse arquivo como a raiz do contêiner binário, e o binário compilado terá o mesmo nome que o nome do pacote.

Módulo (Module)

Para uma engenharia de software, geralmente organizamos de acordo com as normas organizacionais do linguagem de programação usada, a estrutura principal dos módulos organizacionais geralmente é uma árvore. O principal unitário de organização funcional no Java é a classe, enquanto a forma principal de organização de módulos no JavaScript é a função (function).

Essas unidades organizacionais avançadas podem ser contidas em camadas, como a estrutura de diretórios do sistema de arquivos. A unidade organizacional no Rust é o módulo (Module).

mod nation {
    mod government {
        fn govern() {}
    }
    mod congress {
        fn legislate() {}
    }
    mod court {
        fn judicial() {}
    }
}

Este é um texto descritivo do processo do estado de direito: a nação (nación) inclui o governo (government), o congresso (congress) e o tribunal (court), que têm funções administrativas, legislativas e judiciais, respectivamente. Podemos transformá-lo em uma estrutura em árvore:

nación
 ├── government
 │ └── govern
 ├── congress
 │ └── legislate
 └── court
   └── judicial

No sistema de arquivos, a estrutura de diretórios geralmente é representada pelo caractere de barras invertida no string de caminho, o separador de caminho no Rust é ::.

Os caminhos são divididos em caminhos absolutos e relativos. O caminho absoluto começa com a palavra-chave crate. O caminho relativo começa com as palavras-chave self ou super ou um identificador. Por exemplo:

crate::nation::government::govern();

É o caminho absoluto para a função govern, o caminho relativo pode ser representado como:

nation::government::govern();

Agora você pode tentar definir uma estrutura de módulo semelhante em um arquivo fonte e usar o caminho no main.

Se fizer isso, definitivamente descobrirá onde está errado: o módulo government e suas funções são privados(private), você não tem permissão para acessá-los.

Permissões de acesso

No Rust há dois tipos simples de permissões de acesso: público(public) e privado(private).

Por padrão, se não houver modificador, o acesso aos membros do módulo será privado.

Para usar permissões públicas, é necessário usar a palavra-chave pub.

Para módulos privados, apenas em posições de同级 ou inferiores é possível acessá-los, não sendo possível acessá-los externamente.

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    mod congress {
        pub fn legislate() {}
    }
    
    mod court {
        fn judicial() {
            super::congress::legislate();
        }
    }
}
fn main() {
    nation::government::govern();
}

Este programa é compilável. Observe a maneira como o super é acessado no módulo court.

Se um módulo definiu uma estrutura, a estrutura além de ser privada por si só, seus campos também são privados por padrão. Portanto, para usar a estrutura e seus campos do módulo, é necessário declarar pub:

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }
    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}
pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");
    meal.toast = String::from("Wheat");
    println!("Eu gostaria de {} torrão de trigo, por favor", meal.toast);
}
fn main() {
    eat_at_restaurant()
}

Resultado da Execução:

Eu gostaria de um torrão de trigo, por favor

Os itens de enumeração de classes podem conter campos, mas não possuem propriedades semelhantes:

mod SomeModule {
    pub enum Person {
        King {
            name: String
        },
        Quene
    }
}
fn main() {
    let person = SomeModule::Person::King {
        name: String::from("Blue")
    };
    match person {
        SomeModule::Person::King {name} => {
            println!("{}", name);
        }
        _ => {}
    }
}

Resultado da Execução:

Blue

Módulos difíceis de encontrar

Desenvolvedores que usaram Java geralmente odeiam o bloco 'class' mais externo - seu nome é exatamente o mesmo que o nome do arquivo, porque ele representa o contêiner do arquivo, mesmo que seja complicado, não temos outra escolha a não ser escrevê-lo para enfatizar 'esta classe é a classe contida no arquivo'.

No entanto, isso tem algumas vantagens: pelo menos ele faz com que o desenvolvedor perceba claramente a existência do 'encapsulamento de classe', e pode descrever claramente a relação de herança das classes.

No Rust, os módulos são como as classes em Java, mas é possível escrever uma função principal no início do arquivo, como explicar isso?

O conteúdo de cada arquivo Rust é um módulo 'difícil de encontrar'.

Vamos usar dois arquivos para ilustrar isso:

arquivo main.rs

// main.rs
mod second_module;
fn main() {
    println!("Este é o módulo principal.");
    println!("{}", second_module::message());
}

arquivo second_module.rs

// second_module.rs
pub fn message() -> String {
    String::from("Este é o") 2nd module.")
}

Resultado da Execução:

Este é o módulo principal.
Este é o 2nd module.

palavra-chave 'use'

O uso da palavra-chave 'use' pode importar o identificador do módulo para o escopo atual:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
}
use crate::nation::government::govern;
fn main() {
    govern();
}

Este programa pode ser compilado com sucesso.

Como a palavra-chave 'use' importou o identificador 'govern' para o módulo atual, ele pode ser usado diretamente.

Desta forma, resolvemos o problema de caminhos de módulos locais longos.

Claro, em algumas situações, existem dois nomes idênticos que precisam ser importados. Neste caso, podemos usar a palavra-chave 'as' para adicionar um alias ao identificador:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub fn govern() {}
}
    
use crate::nation::government::govern;
use crate::nation::govern as nation_govern;
fn main() {
    nation_govern();
    govern();
}

Aqui há duas funções 'govern', uma sob 'nation' e outra sob 'government'. Usamos 'as' para nomear a sob 'nation' como 'nation_govern'. Ambos os nomes podem ser usados ao mesmo tempo.

A palavra-chave 'use' pode ser usada em conjunto com a palavra-chave 'pub':

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub use government::govern;
}
fn main() {
    nation::govern();
}

Referência à Biblioteca de Referência

Dicionário da Biblioteca Oficial do Rust:https://doc.rust-lang.org/stable/std/all.html

Após aprender os conceitos deste capítulo, podemos facilmente importar bibliotecas do sistema para facilitar o desenvolvimento de programas:

use std::f64::consts::PI;
fn main() {
    println!("{}", (PI / 2.0).sin());
}

Resultado da Execução:

1

Todos os módulos de bibliotecas do sistema são importados por padrão, então, ao usá-los, é só usar a palavra-chave 'use' para simplificar o caminho e facilitar o uso.