Introdução
Bem-vindo aos Cargo Workspaces. Este laboratório faz parte do Rust Book. Você pode praticar suas habilidades em Rust no LabEx.
Neste laboratório, exploraremos o recurso de workspaces do Cargo, que ajuda a gerenciar múltiplos pacotes relacionados desenvolvidos em conjunto, dividindo um pacote em múltiplos crates de biblioteca.
Cargo Workspaces
No Capítulo 12, construímos um pacote que incluía um crate binário e um crate de biblioteca. À medida que seu projeto se desenvolve, você pode descobrir que o crate de biblioteca continua a crescer e deseja dividir seu pacote ainda mais em múltiplos crates de biblioteca. O Cargo oferece um recurso chamado workspaces (espaços de trabalho) que pode ajudar a gerenciar múltiplos pacotes relacionados que são desenvolvidos em conjunto.
Criando um Workspace (Espaço de Trabalho)
Um workspace (espaço de trabalho) é um conjunto de pacotes que compartilham o mesmo Cargo.lock e diretório de saída. Vamos criar um projeto usando um workspace — usaremos código trivial para que possamos nos concentrar na estrutura do workspace. Existem várias maneiras de estruturar um workspace, então mostraremos apenas uma maneira comum. Teremos um workspace contendo um binário e duas bibliotecas. O binário, que fornecerá a funcionalidade principal, dependerá das duas bibliotecas. Uma biblioteca fornecerá uma função add_one e a outra biblioteca uma função add_two. Esses três crates farão parte do mesmo workspace. Começaremos criando um novo diretório para o workspace:
mkdir add
cd add
Em seguida, no diretório add, criamos o arquivo Cargo.toml que configurará todo o workspace. Este arquivo não terá uma seção [package]. Em vez disso, ele começará com uma seção [workspace] que nos permitirá adicionar membros ao workspace, especificando o caminho para o pacote com nosso crate binário; neste caso, esse caminho é adder:
Nome do arquivo: Cargo.toml
[workspace]
members = [
"adder",
]
Em seguida, criaremos o crate binário adder executando cargo new dentro do diretório add:
$ cargo new adder
Created binary (application) `adder` package
Neste ponto, podemos construir o workspace executando cargo build. Os arquivos em seu diretório add devem ser semelhantes a isto:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
O workspace tem um diretório target no nível superior, onde os artefatos compilados serão colocados; o pacote adder não tem seu próprio diretório target. Mesmo se executássemos cargo build de dentro do diretório adder, os artefatos compilados ainda acabariam em add/target em vez de add/adder/target. O Cargo estrutura o diretório target em um workspace assim porque os crates em um workspace devem depender uns dos outros. Se cada crate tivesse seu próprio diretório target, cada crate teria que recompilar cada um dos outros crates no workspace para colocar os artefatos em seu próprio diretório target. Ao compartilhar um diretório target, os crates podem evitar reconstruções desnecessárias.
Criando o Segundo Pacote no Workspace
Em seguida, vamos criar outro pacote membro no workspace e chamá-lo de add_one. Altere o Cargo.toml de nível superior para especificar o caminho add_one na lista members:
Nome do arquivo: Cargo.toml
[workspace]
members = [
"adder",
"add_one",
]
Em seguida, gere um novo crate de biblioteca chamado add_one:
$ cargo new add_one --lib
Created library $(add_one) package
Seu diretório add agora deve ter esses diretórios e arquivos:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
No arquivo add_one/src/lib.rs, vamos adicionar uma função add_one:
Nome do arquivo: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
Agora podemos ter o pacote adder com nosso binário dependendo do pacote add_one que possui nossa biblioteca. Primeiro, precisaremos adicionar uma dependência de caminho em add_one para adder/Cargo.toml:
Nome do arquivo: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
O Cargo não assume que os crates em um workspace dependerão uns dos outros, então precisamos ser explícitos sobre as relações de dependência.
Em seguida, vamos usar a função add_one (do crate add_one) no crate adder. Abra o arquivo adder/src/main.rs e adicione uma linha use no topo para trazer o novo crate de biblioteca add_one para o escopo. Em seguida, altere a função main para chamar a função add_one, como na Listagem 14-7.
Nome do arquivo: adder/src/main.rs
use add_one;
fn main() {
let num = 10;
println!(
"Hello, world! {num} plus one is {}!",
add_one::add_one(num)
);
}
Listagem 14-7: Usando o crate de biblioteca add_one do crate adder
Vamos construir o workspace executando cargo build no diretório add de nível superior!
$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.68s
Para executar o crate binário do diretório add, podemos especificar qual pacote no workspace queremos executar usando o argumento -p e o nome do pacote com cargo run:
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
Isso executa o código em adder/src/main.rs, que depende do crate add_one.
Dependendo de um Pacote Externo em um Workspace
Observe que o workspace tem apenas um arquivo Cargo.lock no nível superior, em vez de ter um Cargo.lock no diretório de cada crate. Isso garante que todos os crates estejam usando a mesma versão de todas as dependências. Se adicionarmos o pacote rand aos arquivos adder/Cargo.toml e add_one/Cargo.toml, o Cargo resolverá ambos para uma versão de rand e registrará isso no único Cargo.lock. Fazer com que todos os crates no workspace usem as mesmas dependências significa que os crates sempre serão compatíveis entre si. Vamos adicionar o crate rand à seção [dependencies] no arquivo add_one/Cargo.toml para que possamos usar o crate rand no crate add_one:
Nome do arquivo: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
Agora podemos adicionar use rand; ao arquivo add_one/src/lib.rs, e construir todo o workspace executando cargo build no diretório add trará e compilará o crate rand. Receberemos um aviso porque não estamos nos referindo ao rand que trouxemos para o escopo:
$ cargo build
Updating crates.io index
Downloaded rand v0.8.5
--snip--
Compiling rand v0.8.5
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 10.18s
O Cargo.lock de nível superior agora contém informações sobre a dependência de add_one em rand. No entanto, embora rand seja usado em algum lugar no workspace, não podemos usá-lo em outros crates no workspace, a menos que adicionemos rand aos seus arquivos Cargo.toml também. Por exemplo, se adicionarmos use rand; ao arquivo adder/src/main.rs para o pacote adder, receberemos um erro:
$ cargo build
--snip--
Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
--> adder/src/main.rs:2:5
|
2 | use rand;
| ^^^^ no external crate `rand`
Para corrigir isso, edite o arquivo Cargo.toml para o pacote adder e indique que rand também é uma dependência para ele. Construir o pacote adder adicionará rand à lista de dependências para adder em Cargo.lock, mas nenhuma cópia adicional de rand será baixada. O Cargo garantiu que cada crate em cada pacote no workspace que usa o pacote rand estará usando a mesma versão, economizando espaço e garantindo que os crates no workspace sejam compatíveis entre si.
Adicionando um Teste a um Workspace
Para outra melhoria, vamos adicionar um teste da função add_one::add_one dentro do crate add_one:
Nome do arquivo: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
Agora execute cargo test no diretório add de nível superior. Executar cargo test em um workspace estruturado como este executará os testes para todos os crates no workspace:
[object Object]
A primeira seção da saída mostra que o teste it_works no crate add_one passou. A próxima seção mostra que zero testes foram encontrados no crate adder, e então a última seção mostra que zero testes de documentação foram encontrados no crate add_one.
Também podemos executar testes para um crate específico em um workspace a partir do diretório de nível superior usando a flag -p e especificando o nome do crate que queremos testar:
[object Object]
Esta saída mostra que cargo test executou apenas os testes para o crate add_one e não executou os testes do crate adder.
Se você publicar os crates no workspace em https://crates.io, cada crate no workspace precisará ser publicado separadamente. Como cargo test, podemos publicar um crate específico em nosso workspace usando a flag -p e especificando o nome do crate que queremos publicar.
Para prática adicional, adicione um crate add_two a este workspace de maneira semelhante ao crate add_one!
À medida que seu projeto cresce, considere usar um workspace: ele fornece componentes individuais menores e mais fáceis de entender do que um grande bloco de código. Além disso, manter os crates em um workspace pode facilitar a coordenação entre os crates se eles forem frequentemente alterados ao mesmo tempo.
Resumo
Parabéns! Você concluiu o laboratório de Cargo Workspaces. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.