quinta-feira, 8 de abril de 2021

Caminhos para fazer referência a um item na árvore de módulo em Rust

Para mostrar ao Rust onde encontrar um item em uma árvore de módulo, usamos um caminho da mesma forma que usamos um caminho ao navegar em um sistema de arquivos. Se quisermos chamar uma função, precisamos saber seu caminho.

Um caminho pode assumir duas formas:

  • Um caminho absoluto começa a partir de uma raiz de crate usando um nome de crate ou um literal crate.
  • Um caminho relativo começa a partir do módulo atual e usa self, super ou um identificador no módulo atual.

Os caminhos absolutos e relativos são seguidos por um ou mais identificadores separados por dois pontos duplos (::).

Voltemos ao exemplo da Listagem 7-1. Como chamamos a função adicionar_a_lista_de_espera? Isso é o mesmo que perguntar: qual é o caminho da função adicionar_a_lista_de_espera? Na Listagem 7-3, simplificamos um pouco nosso código removendo alguns dos módulos e funções. Mostraremos duas maneiras de chamar a função adicionar_a_lista_de_espera a partir de uma nova função comer_no_restaurante definida na raiz do crate. A função comer_no_restaurante faz parte da API pública do nosso crate de biblioteca, então a marcamos com a palavra-chave pub. Na seção “Expondo caminhos com a palavra-chave pub”, entraremos em mais detalhes sobre pub. Observe que este exemplo ainda não será compilado; vamos explicar o porquê daqui a pouco.

Nome do arquivo: src/lib.rs

Esse código não compila Esse código não compila.

mod frente_da_casa {
    mod hospedagem {
        fn adicionar_a_lista_de_espera() {}
    }
}

pub fn comer_no_restaurante() {
    // Caminho absoluto
    crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();

    // Caminho relativo
    frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-3: Chamando a adicionar_a_lista_de_esperafunção usando caminhos absolutos e relativos.

Na primeira vez que chamamos a função adicionar_a_lista_de_espera em comer_no_restaurante, usamos um caminho absoluto. A função adicionar_a_lista_de_espera é definida no mesmo crate que comer_no_restaurante, o que significa que podemos usar a palavra-chave crate para iniciar um caminho absoluto.

Depois de crate, incluímos cada um dos módulos sucessivos até chegarmos a adicionar_a_lista_de_espera. Você pode imaginar um sistema de arquivos com a mesma estrutura, e nós especificaríamos o caminho /frente_da_casa/hospedagem/adicionar_a_lista_de_espera para executar o programa adicionar_a_lista_de_espera; usar o nome crate para iniciar a partir da raiz do crate é como usar / para iniciar a partir da raiz do sistema de arquivos em seu shell.

A segunda vez que chamar adicionar_a_lista_de_espera em comer_no_restaurante, usamos um caminho relativo. O caminho começa com frente_da_casa, o nome do módulo definido no mesmo nível da árvore de módulos que comer_no_restaurante. Aqui, o equivalente do sistema de arquivos seria o uso do caminho frente_da_casa/hospedagem/adicionar_a_lista_de_espera. Começar com um nome significa que o caminho é relativo.

A escolha de usar um caminho relativo ou absoluto é uma decisão que você tomará com base em seu projeto. A decisão deve depender da probabilidade de você mover o código de definição do item separadamente ou junto com o código que usa o item. Por exemplo, se movermos o módulo frente_da_casa e a função comer_no_restaurante para um módulo denominado esperiencia_do_cliente, precisaremos atualizar o caminho absoluto para adicionar_a_lista_de_espera, mas o caminho relativo ainda será válido. No entanto, se movêssemos a função comer_no_restaurante separadamente para um módulo denominado jantando, o caminho absoluto para a chamada adicionar_a_lista_de_espera permaneceria o mesmo, mas o caminho relativo precisaria ser atualizado. Nossa preferência é especificar caminhos absolutos porque é mais provável mover definições de código e chamadas de item independentemente umas das outras.

Vamos tentar compilar a Listagem 7-3 e descobrir por que ela ainda não compila! O erro que obtemos é mostrado na Listagem 7-4.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hospedagem` is private
 --> src/lib.rs:9:28
  |
9 |     crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
  |                            ^^^^^^^ private module
  |
note: the module `hospedagem` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hospedagem {
  |     ^^^^^^^^^^^

error[E0603]: module `hospedagem` is private
  --> src/lib.rs:12:21
   |
12 |     frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
   |                     ^^^^^^^ private module
   |
note: the module `hospedagem` is defined here
  --> src/lib.rs:2:5
   |
2  |     mod hospedagem {
   |     ^^^^^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`

To learn more, run the command again with --verbose.

Listagem 7-4: Erros do compilador na construção do código na Listagem 7-3.

As mensagens de erro indicam que o módulo hospedagem é privado. Em outras palavras, temos os caminhos corretos para o módulo hospedagem e a função adicionar_a_lista_de_espera, mas Rust não nos deixa usá-los porque não tem acesso às seções privadas.

Módulos não são úteis apenas para organizar seu código. Eles também definem o limite de privacidade em Rust: a linha que encapsula os detalhes de implementação que o código externo não tem permissão para conhecer, chamar ou confiar. Portanto, se você quiser tornar um item como uma função ou estrutura privada, coloque-o em um módulo.

A forma como a privacidade funciona no Rust é que todos os itens (funções, métodos, estruturas, enums, módulos e constantes) são privados por padrão. Os itens em um módulo pai não podem usar os itens privados dentro dos módulos filhos, mas os itens nos módulos filhos podem usar os itens em seus módulos ancestrais. O motivo é que os módulos filhos envolvem e ocultam seus detalhes de implementação, mas os módulos filhos podem ver o contexto no qual estão definidos. Para continuar com a metáfora do restaurante, pense nas regras de privacidade como sendo o back office de um restaurante: o que acontece lá é privado para os clientes do restaurante, mas os gerentes de escritório podem ver e fazer tudo no restaurante em que operam.

Rust optou por fazer com que o sistema de módulo funcionasse dessa maneira, de modo que ocultar detalhes de implementação internos seja o padrão. Dessa forma, você sabe quais partes do código interno podem ser alteradas sem quebrar o código externo. Mas você pode expor partes internas do código dos módulos filhos para módulos ancestrais externos usando a palavra-chave pub para tornar um item público.

Expondo caminhos com a palavra-chave pub

Voltemos ao erro na Listagem 7-4 que nos disse que o módulo hospedagem é privado. Queremos que a função comer_no_restaurante no módulo pai tenha acesso à função adicionar_a_lista_de_espera no módulo filho, portanto, marcamos o módulo hospedagem com a palavra-chave pub, conforme mostrado na Listagem 7-5.

Nome do arquivo: src/lib.rs

Esse código não compila Esse código não compila.

mod frente_da_casa {
    pub mod hospedagem {
        fn adicionar_a_lista_de_espera() {}
    }
}

pub fn comer_no_restaurante() {
    // Caminho absoluto
    crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();

    // Caminho relativo
    frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-5: Declarando o módulo hospedagem como pub para usá-lo de comer_no_restaurante

Infelizmente, o código na Listagem 7-5 ainda resulta em um erro, conforme mostrado na Listagem 7-6.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `adicionar_a_lista_de_espera` is private
 --> src/lib.rs:9:37
  |
9 |     crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
  |                                     ^^^^^^^^^^^^^^^ private function
  |
note: the function `adicionar_a_lista_de_espera` is defined here
 --> src/lib.rs:3:9
  |
3 |         fn adicionar_a_lista_de_espera() {}
  |         ^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `adicionar_a_lista_de_espera` is private
  --> src/lib.rs:12:30
   |
12 |     frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
   |                              ^^^^^^^^^^^^^^^ private function
   |
note: the function `adicionar_a_lista_de_espera` is defined here
  --> src/lib.rs:3:9
   |
3  |         fn adicionar_a_lista_de_espera() {}
   |         ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`

To learn more, run the command again with --verbose.

Listagem 7-6: Erros do compilador na construção do código na Listagem 7-5

O que aconteceu? Adicionar a palavra-chave pub antes de mod hospedagem torna o módulo público. Com essa mudança, se podemos acessar frente_da_casa, podemos acessar hospedagem. Mas o conteúdo de hospedagem ainda é privado; tornar o módulo público não torna seu conteúdo público. A palavra-chave pub em um módulo permite apenas que o código em seus módulos ancestrais se refira a ele.

Os erros na Listagem 7-6 dizem que a função adicionar_a_lista_de_espera é privada. As regras de privacidade se aplicam a structs, enums, funções e métodos, bem como a módulos.

Também tornemos a função adicionar_a_lista_de_espera pública adicionando a palavra-chave pub antes de sua definição, como na Listagem 7-7.

Nome do arquivo: src/lib.rs

mod frente_da_casa {
    pub mod hospedagem {
        pub fn adicionar_a_lista_de_espera() {}
    }
}

pub fn comer_no_restaurante() {
    // Caminho absoluto
    crate::frente_da_casa::hospedagem::adicionar_a_lista_de_espera();

    // Caminho relativo
    frente_da_casa::hospedagem::adicionar_a_lista_de_espera();
}

Listagem 7-7: Adicionando a palavra-chave pub a mod hospedagem e fn adicionar_a_lista_de_espera permite-nos chamar a função de comer_no_restaurante

Agora o código será compilado! vamos dar uma olhada no caminho absoluto e relativo e verificar por que adicionar a palavra-chave pub nos permite usar esses caminhos em adicionar_a_lista_de_espera com relação às regras de privacidade.

No caminho absoluto, começamos com crate a raiz da árvore de módulos do nosso crate. Em seguida, o módulo frente_da_casa é definido na raiz do crate. O módulo frente_da_casa não é público, mas como a função comer_no_restaurante é definida no mesmo módulo que frente_da_casa (ou seja, comer_no_restaurante e frente_da_casa são irmãos), podemos nos referir a frente_da_casa a partir de comer_no_restaurante. O próximo é o módulo hospedagem marcado com pub. Podemos acessar o módulo pai de hospedagem, para que possamos acessar hospedagem. Finalmente, a função adicionar_a_lista_de_espera é marcada com pub e podemos acessar seu módulo pai, então esta chamada de função funciona!

No caminho relativo, a lógica é a mesma que o caminho absoluto, exceto para a primeira etapa: em vez de começar na raiz do crate, o caminho começa em frente_da_casa. O módulo frente_da_casa é definido dentro do mesmo módulo que comer_no_restaurante, portanto, o caminho relativo a partir do módulo no qual comer_no_restaurante está definido funciona. Então, porque hospedagem e adicionar_a_lista_de_espera estão marcados com pub, o resto do caminho funciona, e esta chamada de função é válida!

Iniciando caminhos relativos com super

Também podemos construir caminhos relativos que começam no módulo pai usando super no início do caminho. É como iniciar um caminho de sistema de arquivos com a sintaxe ... Por que queremos fazer isso?

Considere o código na Listagem 7-8 que modela a situação em que um chef corrige um pedido incorreto e o apresenta pessoalmente ao cliente. A função consertar_pedido_incorreto chama a função servir_pedido especificando o caminho para servir_pedido começar com super:

Nome do arquivo: src/lib.rs

fn servir_pedido() {}

mod fundo_da_casa {
    fn consertar_pedido_incorreto() {
        preparar_pedido();
        super::servir_pedido();
    }

    fn preparar_pedido() {}
}

Listagem 7-8: Chamando uma função usando um caminho relativo começando com super

A função consertar_pedido_incorreto está no módulo fundo_da_casa, então podemos usar super para ir para o módulo pai de fundo_da_casa, que neste caso é a raiz crate. A partir daí, procuramos servir_pedido e encontramos. Sucesso! Achamos que o módulo fundo_da_casa e a função servir_pedido provavelmente permanecerão na mesma relação um com o outro e serão movidos juntos se decidirmos reorganizar a árvore de módulos do crate. Portanto, usamos super por isso, teremos menos lugares para atualizar o código no futuro, se esse código for movido para um módulo diferente.

Tornando Structs e Enums Públicos

Também podemos usar pub para designar structs e enums como públicos, mas há alguns detalhes extras. Se usarmos pub antes de uma definição de estrutura, tornamos a estrutura pública, mas os campos da estrutura ainda serão privados. Podemos tornar cada campo público ou não, caso a caso. Na Listagem 7-9, definimos uma estrutura fundo_da_casa::cafeDaManha pública com um campo torada público, mas um campo fruta_temporada privado. Isso modela o caso de um restaurante onde o cliente pode escolher o tipo de pão que vem com a refeição, mas o chef decide qual fruta acompanhará a refeição com base na temporada e no estoque. As frutas disponíveis mudam rapidamente, então os clientes não podem escolher a fruta ou mesmo ver quais frutas irão receber.

Nome do arquivo: src/lib.rs

mod fundo_da_casa {
    pub struct cafeDaManha {
        pub torada: String,
        fruta_temporada: String,
    }

    impl cafeDaManha {
        pub fn verao(torada: &str) -> cafeDaManha {
            cafeDaManha {
                torada: String::from(torada),
                fruta_temporada: String::from("Pessegos"),
            }
        }
    }
}

pub fn comer_no_restaurante() {
    // Peça um café da manhã no verão com torradas de centeio
    let mut meal = fundo_da_casa::cafeDaManha::verao("Centeio");
    // Mudar de ideia sobre o pão que gostaríamos
    meal.torada = String::from("Trigo");
    println!("Eu gostaria de torrada de {} por favor", meal.torada);

    // A próxima linha não será compilada se descomentarmos; não foram permitidos
    // para ver ou modificar as frutas da estação que vêm com a refeição
    // meal.fruta_temporada = String::from("amoras");
}

Listagem 7-9: Uma estrutura com alguns campos públicos e alguns campos privados

Como o campo torada na estrutura fundo_da_casa::cafeDaManha é público, no comer_no_restaurante podemos escrever e ler do campo torada usando a notação de ponto. Observe que não podemos usar o campo fruta_temporada em comer_no_restaurante porque fruta_temporada é privado. Tente remover o comentário da linha modificando o valor do campo fruta_temporada para ver qual erro você obtém!

Além disso, observe que, por fundo_da_casa::cafeDaManha ter um campo privado, a estrutura precisa fornecer uma função pública associada que constrói uma instância de cafeDaManha (nós a nomeamos verao aqui). Se cafeDaManha não tivéssemos essa função, não poderíamos criar uma instância de cafeDaManha no comer_no_restaurante porque não poderíamos definir o valor do campo privado fruta_temporada em comer_no_restaurante.

Em contraste, se tornarmos um enum público, todas as suas variantes serão públicas. Precisamos apenas da palavra-chave pub antes de enum, conforme mostrado na Listagem 7-10.

Nome do arquivo: src/lib.rs

mod fundo_da_casa {
    pub enum Appetizer {
        Sopa,
        Salada,
    }
}

pub fn comer_no_restaurante() {
    let pedido1 = fundo_da_casa::Appetizer::Sopa;
    let pedido2 = fundo_da_casa::Appetizer::Salada;
}

Listagem 7-10: Designar um enum como público torna todas as suas variantes públicas

Como tornamos o enum Appetizer público, podemos usar as variantes Sopa e Salada em comer_no_restaurante. Enums não são muito úteis, a menos que suas variantes sejam públicas; seria irritante ter que anotar todas as variantes de enum pub em todos os casos, então o padrão para variantes de enum é ser público. As estruturas são frequentemente úteis sem que seus campos sejam públicos, portanto, os campos de estrutura seguem a regra geral de tudo ser privado por padrão, a menos que anotado com pub.

Há mais uma situação envolvendo pub que não cobrimos, e esse é nosso último recurso do sistema de módulo: a palavra-chave use. Vamos cobrir use por si só primeiro e, em seguida, mostraremos como combinar pub e use.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

0 comentários:

Postar um comentário