Publicando uma Crate no Crates.io

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Bem-vindo(a) a Publicando um Crate no Crates.io. Este laboratório faz parte do Livro de Rust. Você pode praticar suas habilidades em Rust no LabEx.

Neste laboratório, discutiremos como publicar um crate no crates.io, o registro de crates que distribui código de código aberto, tornando mais fácil para as pessoas encontrarem e usarem seu pacote.

Publicando um Crate no Crates.io

Usamos pacotes de https://crates.io como dependências do nosso projeto, mas você também pode compartilhar seu código com outras pessoas publicando seus próprios pacotes. O registro de crates em https://crates.io distribui o código-fonte dos seus pacotes, portanto, ele hospeda principalmente código de código aberto.

Rust e Cargo possuem recursos que tornam seu pacote publicado mais fácil de ser encontrado e usado pelas pessoas. Falaremos sobre alguns desses recursos a seguir e, em seguida, explicaremos como publicar um pacote.

Criando Comentários de Documentação Úteis

Documentar seus pacotes com precisão ajudará outros usuários a saber como e quando usá-los, por isso vale a pena investir tempo na escrita da documentação. No Capítulo 3, discutimos como comentar o código Rust usando duas barras, //. Rust também possui um tipo específico de comentário para documentação, conhecido convenientemente como comentário de documentação (documentation comment), que gerará documentação HTML. O HTML exibe o conteúdo dos comentários de documentação para itens de API pública destinados a programadores interessados em saber como usar seu crate, em oposição a como seu crate é implementado.

Comentários de documentação usam três barras, ///, em vez de duas e suportam a notação Markdown para formatar o texto. Coloque os comentários de documentação logo antes do item que eles estão documentando. A Listagem 14-1 mostra comentários de documentação para uma função add_one em um crate chamado my_crate.

Nome do arquivo: src/lib.rs

/// Adiciona um ao número fornecido.
///
/// ## Exemplos
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```rust
pub fn add_one(x: i32) -> i32 {
    x + 1
}

Listagem 14-1: Um comentário de documentação para uma função

Aqui, fornecemos uma descrição do que a função add_one faz, iniciamos uma seção com o título Exemplos e, em seguida, fornecemos código que demonstra como usar a função add_one. Podemos gerar a documentação HTML a partir deste comentário de documentação executando cargo doc. Este comando executa a ferramenta rustdoc distribuída com Rust e coloca a documentação HTML gerada no diretório target/doc.

Para conveniência, executar cargo doc --open construirá o HTML para a documentação do seu crate atual (bem como a documentação para todas as dependências do seu crate) e abrirá o resultado em um navegador da web. Navegue até a função add_one e você verá como o texto nos comentários de documentação é renderizado, conforme mostrado na Figura 14-1.

Figura 14-1: Documentação HTML para a função add_one

Seções Comumente Usadas

Usamos o título Markdown ## Exemplos na Listagem 14-1 para criar uma seção no HTML com o título "Exemplos". Aqui estão algumas outras seções que os autores de crates comumente usam em sua documentação:

  • Panics (Pânicos): Os cenários em que a função que está sendo documentada pode entrar em pânico. Os chamadores da função que não querem que seus programas entrem em pânico devem garantir que não chamem a função nessas situações.
  • Errors (Erros): Se a função retornar um Result, descrever os tipos de erros que podem ocorrer e quais condições podem fazer com que esses erros sejam retornados pode ser útil para os chamadores, para que eles possam escrever código para lidar com os diferentes tipos de erros de maneiras diferentes.
  • Safety (Segurança): Se a função for unsafe para chamar (discutimos a insegurança no Capítulo 19), deve haver uma seção explicando por que a função é insegura e cobrindo os invariantes que a função espera que os chamadores mantenham.

A maioria dos comentários de documentação não precisa de todas essas seções, mas esta é uma boa lista de verificação para lembrá-lo dos aspectos do seu código que os usuários estarão interessados em saber.

Comentários de Documentação como Testes

Adicionar blocos de código de exemplo em seus comentários de documentação pode ajudar a demonstrar como usar sua biblioteca, e fazê-lo tem um bônus adicional: executar cargo test executará os exemplos de código em sua documentação como testes! Nada é melhor do que documentação com exemplos. Mas nada é pior do que exemplos que não funcionam porque o código foi alterado desde que a documentação foi escrita. Se executarmos cargo test com a documentação para a função add_one da Listagem 14-1, veremos uma seção nos resultados do teste que se parece com isto:

   Doc-tests my_crate

running 1 test
test src/lib.rs - add_one (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.27s

Agora, se alterarmos a função ou o exemplo para que o assert_eq! no exemplo entre em pânico e executarmos cargo test novamente, veremos que os testes de documentação detectam que o exemplo e o código estão fora de sincronia um com o outro!

Comentando Itens Contidos

O comentário de documentação //! adiciona documentação ao item que contém os comentários, em vez de aos itens seguindo os comentários. Normalmente, usamos esses comentários de documentação dentro do arquivo raiz da crate (src/lib.rs por convenção) ou dentro de um módulo para documentar a crate ou o módulo como um todo.

Por exemplo, para adicionar documentação que descreve o propósito da crate my_crate que contém a função add_one, adicionamos comentários de documentação que começam com //! ao início do arquivo src/lib.rs, conforme mostrado na Listagem 14-2.

Nome do arquivo: src/lib.rs

//! ## My Crate
//!
//! `my_crate` is a collection of utilities to make performing
//! certain calculations more convenient.

/// Adds one to the number given.
--snip--

Listagem 14-2: Documentação para a crate my_crate como um todo

Observe que não há nenhum código após a última linha que começa com //!. Como iniciamos os comentários com //! em vez de ///, estamos documentando o item que contém este comentário, em vez de um item que segue este comentário. Neste caso, esse item é o arquivo src/lib.rs, que é a raiz da crate. Esses comentários descrevem toda a crate.

Quando executamos cargo doc --open, esses comentários serão exibidos na página inicial da documentação para my_crate acima da lista de itens públicos na crate, conforme mostrado na Figura 14-2.

Figura 14-2: Documentação renderizada para my_crate, incluindo o comentário que descreve a crate como um todo

Comentários de documentação dentro de itens são úteis para descrever crates e módulos, especialmente. Use-os para explicar o propósito geral do contêiner para ajudar seus usuários a entender a organização da crate.

Exportando uma API Pública Conveniente com pub use

A estrutura da sua API pública é uma consideração importante ao publicar uma crate. As pessoas que usam sua crate estão menos familiarizadas com a estrutura do que você e podem ter dificuldade em encontrar as partes que desejam usar se sua crate tiver uma grande hierarquia de módulos.

No Capítulo 7, abordamos como tornar os itens públicos usando a palavra-chave pub e como trazer itens para um escopo com a palavra-chave use. No entanto, a estrutura que faz sentido para você enquanto está desenvolvendo uma crate pode não ser muito conveniente para seus usuários. Você pode querer organizar suas structs em uma hierarquia contendo vários níveis, mas então as pessoas que desejam usar um tipo que você definiu profundamente na hierarquia podem ter problemas para descobrir que esse tipo existe. Elas também podem ficar irritadas por ter que inserir use my_crate::some_module::another_module::UsefulType; em vez de use my_crate::UsefulType;.

A boa notícia é que, se a estrutura não for conveniente para outros usarem de outra biblioteca, você não precisa reorganizar sua organização interna: em vez disso, você pode reexportar itens para criar uma estrutura pública que seja diferente da sua estrutura privada usando pub use. Reexportar pega um item público em um local e o torna público em outro local, como se fosse definido no outro local.

Por exemplo, digamos que criamos uma biblioteca chamada art para modelar conceitos artísticos. Dentro desta biblioteca, existem dois módulos: um módulo kinds contendo dois enums chamados PrimaryColor e SecondaryColor e um módulo utils contendo uma função chamada mix, conforme mostrado na Listagem 14-3.

Nome do arquivo: src/lib.rs

//! ## Art
//!
//! A library for modeling artistic concepts.

pub mod kinds {
    /// The primary colors according to the RYB color model.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// The secondary colors according to the RYB color model.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    use crate::kinds::*;

    /// Combines two primary colors in equal amounts to create
    /// a secondary color.
    pub fn mix(
        c1: PrimaryColor,
        c2: PrimaryColor,
    ) -> SecondaryColor {
        --snip--
    }
}

Listagem 14-3: Uma biblioteca art com itens organizados em módulos kinds e utils

A Figura 14-3 mostra como seria a página inicial da documentação para esta crate gerada por cargo doc.

Figura 14-3: Página inicial da documentação para art que lista os módulos kinds e utils

Observe que os tipos PrimaryColor e SecondaryColor não estão listados na página inicial, nem a função mix. Precisamos clicar em kinds e utils para vê-los.

Outra crate que depende desta biblioteca precisaria de declarações use que trouxessem os itens de art para o escopo, especificando a estrutura do módulo que está atualmente definida. A Listagem 14-4 mostra um exemplo de uma crate que usa os itens PrimaryColor e mix da crate art.

Nome do arquivo: src/main.rs

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}

Listagem 14-4: Uma crate usando os itens da crate art com sua estrutura interna exportada

O autor do código na Listagem 14-4, que usa a crate art, teve que descobrir que PrimaryColor está no módulo kinds e mix está no módulo utils. A estrutura do módulo da crate art é mais relevante para os desenvolvedores que trabalham na crate art do que para aqueles que a usam. A estrutura interna não contém nenhuma informação útil para alguém que tenta entender como usar a crate art, mas sim causa confusão porque os desenvolvedores que a usam precisam descobrir onde procurar e devem especificar os nomes dos módulos nas declarações use.

Para remover a organização interna da API pública, podemos modificar o código da crate art na Listagem 14-3 para adicionar declarações pub use para reexportar os itens no nível superior, conforme mostrado na Listagem 14-5.

Nome do arquivo: src/lib.rs

//! ## Art
//!
//! A library for modeling artistic concepts.

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
    --snip--
}

pub mod utils {
    --snip--
}

Listagem 14-5: Adicionando declarações pub use para reexportar itens

A documentação da API que cargo doc gera para esta crate agora listará e vinculará as reexportações na página inicial, conforme mostrado na Figura 14-4, tornando os tipos PrimaryColor e SecondaryColor e a função mix mais fáceis de encontrar.

Figura 14-4: A página inicial da documentação para art que lista as reexportações

Os usuários da crate art ainda podem ver e usar a estrutura interna da Listagem 14-3, conforme demonstrado na Listagem 14-4, ou podem usar a estrutura mais conveniente na Listagem 14-5, conforme mostrado na Listagem 14-6.

Nome do arquivo: src/main.rs

use art::mix;
use art::PrimaryColor;

fn main() {
    --snip--
}

Listagem 14-6: Um programa usando os itens reexportados da crate art

Em casos em que há muitos módulos aninhados, reexportar os tipos no nível superior com pub use pode fazer uma diferença significativa na experiência das pessoas que usam a crate. Outro uso comum de pub use é reexportar definições de uma dependência na crate atual para tornar as definições dessa crate parte da API pública da sua crate.

Criar uma estrutura de API pública útil é mais uma arte do que uma ciência, e você pode iterar para encontrar a API que funciona melhor para seus usuários. Escolher pub use oferece flexibilidade na forma como você estrutura sua crate internamente e desacopla essa estrutura interna do que você apresenta aos seus usuários. Observe algum código de crates que você instalou para ver se sua estrutura interna difere de sua API pública.

Configurando uma Conta no Crates.io

Antes de poder publicar qualquer crate, você precisa criar uma conta em https://crates.io e obter um token de API. Para fazer isso, visite a página inicial em https://crates.io e faça login através de uma conta do GitHub. (A conta do GitHub é atualmente um requisito, mas o site pode suportar outras formas de criar uma conta no futuro.) Depois de fazer login, visite as configurações da sua conta em https://crates.io/me e recupere sua chave de API. Em seguida, execute o comando cargo login com sua chave de API, assim:

cargo login abcdefghijklmnopqrstuvwxyz012345

Este comando informará ao Cargo sobre seu token de API e o armazenará localmente em ~/.cargo/credentials. Observe que este token é um segredo: não o compartilhe com mais ninguém. Se você o compartilhar com alguém por qualquer motivo, deverá revogá-lo e gerar um novo token em https://crates.io.

Adicionando Metadados a uma Nova Crate

Digamos que você tenha uma crate que deseja publicar. Antes de publicar, você precisará adicionar alguns metadados na seção [package] do arquivo Cargo.toml da crate.

Sua crate precisará de um nome único. Enquanto você estiver trabalhando em uma crate localmente, você pode nomeá-la como quiser. No entanto, os nomes das crates em https://crates.io são alocados por ordem de chegada. Uma vez que um nome de crate é usado, ninguém mais pode publicar uma crate com esse nome. Antes de tentar publicar uma crate, pesquise o nome que você deseja usar. Se o nome já foi usado, você precisará encontrar outro nome e editar o campo name no arquivo Cargo.toml na seção [package] para usar o novo nome para publicação, assim:

Nome do arquivo: Cargo.toml

[package]
name = "guessing_game"

Mesmo que você tenha escolhido um nome único, ao executar cargo publish para publicar a crate neste ponto, você receberá um aviso e, em seguida, um erro:

$ cargo publish
    Updating crates.io index
warning: manifest has no description, license, license-file, documentation,
homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata
for more info.
--snip--
error: failed to publish to registry at https://crates.io

Caused by:
  the remote server responded with an error: missing or empty metadata fields:
description, license. Please see https://doc.rust-
lang.org/cargo/reference/manifest.html for how to upload metadata

Isso resulta em um erro porque você está faltando algumas informações cruciais: uma descrição e uma licença são necessárias para que as pessoas saibam o que sua crate faz e sob quais termos elas podem usá-la. Em Cargo.toml, adicione uma descrição que seja apenas uma ou duas frases, porque ela aparecerá com sua crate nos resultados da pesquisa. Para o campo license, você precisa fornecer um valor de identificador de licença. O Software Package Data Exchange (SPDX) da Linux Foundation em http://spdx.org/licenses lista os identificadores que você pode usar para este valor. Por exemplo, para especificar que você licenciou sua crate usando a Licença MIT, adicione o identificador MIT:

Nome do arquivo: Cargo.toml

[package]
name = "guessing_game"
license = "MIT"

Se você deseja usar uma licença que não aparece no SPDX, você precisa colocar o texto dessa licença em um arquivo, incluir o arquivo em seu projeto e, em seguida, usar license-file para especificar o nome desse arquivo em vez de usar a chave license.

Orientação sobre qual licença é apropriada para seu projeto está além do escopo deste livro. Muitas pessoas na comunidade Rust licenciam seus projetos da mesma forma que o Rust, usando uma licença dupla de MIT OR Apache-2.0. Essa prática demonstra que você também pode especificar vários identificadores de licença separados por OR para ter várias licenças para seu projeto.

Com um nome único, a versão, sua descrição e uma licença adicionadas, o arquivo Cargo.toml para um projeto que está pronto para ser publicado pode se parecer com isto:

Nome do arquivo: Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
description = "A fun game where you guess what number the
computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]

A documentação do Cargo em https://doc.rust-lang.org/cargo descreve outros metadados que você pode especificar para garantir que outros possam descobrir e usar sua crate com mais facilidade.

Publicando no Crates.io

Agora que você criou uma conta, salvou seu token de API, escolheu um nome para sua crate e especificou os metadados necessários, você está pronto para publicar! Publicar uma crate faz o upload de uma versão específica para https://crates.io para que outros possam usar.

Tenha cuidado, porque uma publicação é permanente. A versão nunca pode ser sobrescrita e o código não pode ser excluído. Um dos principais objetivos do Crates.io é atuar como um arquivo permanente de código para que as compilações de todos os projetos que dependem de crates de https://crates.io continuem funcionando. Permitir exclusões de versões tornaria impossível cumprir esse objetivo. No entanto, não há limite para o número de versões de crate que você pode publicar.

Execute o comando cargo publish novamente. Ele deve ter sucesso agora:

$ cargo publish
    Updating crates.io index
   Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
   Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
   Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
   Uploading guessing_game v0.1.0 (file:///projects/guessing_game)

Parabéns! Agora você compartilhou seu código com a comunidade Rust, e qualquer pessoa pode facilmente adicionar sua crate como uma dependência de seu projeto.

Publicando uma Nova Versão de uma Crate Existente

Quando você fez alterações em sua crate e está pronto para lançar uma nova versão, você altera o valor version especificado em seu arquivo Cargo.toml e republica. Use as regras de Versionamento Semântico (Semantic Versioning) em http://semver.org para decidir qual é o número da próxima versão apropriada, com base nos tipos de alterações que você fez. Em seguida, execute cargo publish para fazer o upload da nova versão.

Descontinuando Versões do Crates.io com cargo yank

Embora você não possa remover versões anteriores de uma crate, você pode impedir que projetos futuros as adicionem como uma nova dependência. Isso é útil quando uma versão da crate está quebrada por um motivo ou outro. Em tais situações, o Cargo suporta o "yanking" de uma versão da crate.

Yanking uma versão impede que novos projetos dependam dessa versão, permitindo que todos os projetos existentes que dependem dela continuem. Essencialmente, um yank significa que todos os projetos com um Cargo.lock não serão interrompidos, e quaisquer futuros arquivos Cargo.lock gerados não usarão a versão yanked.

Para fazer o yank de uma versão de uma crate, no diretório da crate que você publicou anteriormente, execute cargo yank e especifique qual versão você deseja fazer o yank. Por exemplo, se publicamos uma crate chamada guessing_game versão 1.0.1 e queremos fazer o yank dela, no diretório do projeto para guessing_game executaríamos:

$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1

Adicionando --undo ao comando, você também pode desfazer um yank e permitir que os projetos comecem a depender de uma versão novamente:

$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1

Um yank não exclui nenhum código. Ele não pode, por exemplo, excluir segredos carregados acidentalmente. Se isso acontecer, você deve redefinir esses segredos imediatamente.

Resumo

Parabéns! Você concluiu o laboratório Publicando uma Crate no Crates.io. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.