Pacote de ferrugem com uma biblioteca e um binário?

190

Eu gostaria de criar um pacote Rust que contenha uma biblioteca reutilizável (onde a maior parte do programa é implementada) e também um executável que o utilize.

Supondo que não tenha confundido nenhuma semântica no sistema do módulo Rust, como deve ser meu Cargo.tomlarquivo?

Andrew Wagner
fonte

Respostas:

205
Tok:tmp doug$ du -a

8   ./Cargo.toml
8   ./src/bin.rs
8   ./src/lib.rs
16  ./src

Cargo.toml:

[package]
name = "mything"
version = "0.0.1"
authors = ["me <[email protected]>"]

[lib]
name = "mylib"
path = "src/lib.rs"

[[bin]]
name = "mybin"
path = "src/bin.rs"

src / lib.rs:

pub fn test() {
    println!("Test");
}

src / bin.rs:

extern crate mylib; // not needed since Rust edition 2018

use mylib::test;

pub fn main() {
    test();
}
Doug
fonte
2
Obrigado Doug, vou tentar! As anotações #! [Crate_name =] e #! [Crate_type] são opcionais?
Andrew Wagner
4
Quando você usa o Cargo, essas opções são desnecessárias porque o Cargo as passa como sinalizadores do compilador. Se você executar cargo build --verbose, você os verá na rustclinha de comando.
Vladimir Matveev
33
Você sabe por que [[bin]]é uma matriz de tabelas? Por que usar [[bin]]e não [bin]? Parece não haver documentação sobre isso.
CMCDragonkai
40
@CMCDragonkai É a especificação do formato toml [[x]] é uma matriz uma vez desserializada; ie uma única caixa pode produzir vários binários, mas apenas uma biblioteca (portanto, [lib], não [[lib]]). Você pode ter várias seções de compartimento. (Eu concordo, isso parece estranho, mas o toml sempre foi uma escolha controversa).
Doug
1
Existe uma maneira de impedir que ele compile o binário quando tudo o que eu quero é a lib? O binário possui dependências adicionais que adiciono por meio de um recurso chamado "binário"; quando tento compilá-lo sem esse recurso, ele falha ao compilar. Ele reclama que não conseguiu encontrar as caixas que o bin.rs está tentando importar.
precisa saber é o seguinte
150

Você também pode colocar fontes binárias src/bine o restante das fontes src. Você pode ver um exemplo no meu projeto . Você não precisa modificar o seu Cargo.toml, e cada arquivo de origem será compilado em um binário de mesmo nome.

A configuração da outra resposta é substituída por:

$ tree
.
├── Cargo.toml
└── src
    ├── bin
    │   └── mybin.rs
    └── lib.rs

Cargo.toml

[package]
name = "example"
version = "0.0.1"
authors = ["An Devloper <[email protected]>"]

src / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

src / bin / mybin.rs

extern crate example; // Optional in Rust 2018

fn main() {
    println!("I'm using the library: {:?}", example::really_complicated_code(1, 2));
}

E execute:

$ cargo run --bin mybin
I'm using the library: Ok(3)

Além disso, você pode apenas criar um src/main.rsque será usado como executável padrão. Infelizmente, isso entra em conflito com o cargo doccomando:

Não é possível documentar um pacote em que uma biblioteca e um binário tenham o mesmo nome. Considere renomear um ou marcar o destino comodoc = false

Shepmaster
fonte
13
se encaixa bem com a abordagem de convenção sobre configuração da ferrugem! ambas as respostas juntas e você tem uma grande conveniência e flexibilidade.
ovelha voadora
9
extern crate example;não é exigida a partir de ferrugem 2018, você pode escrever diretamente use example::really_complicated_code;e usar a função sem nomear o escopo
Sassman
47

Uma solução alternativa é realmente não tentar empilhar as duas coisas em um único pacote. Para projetos um pouco maiores com um executável amigável, achei muito bom usar um espaço de trabalho

Criamos um projeto binário que inclui uma biblioteca dentro dele:

the-binary
├── Cargo.lock
├── Cargo.toml
├── mylibrary
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── src
    └── main.rs

Cargo.toml

Isso usa a [workspace]chave e depende da biblioteca:

[package]
name = "the-binary"
version = "0.1.0"
authors = ["An Devloper <[email protected]>"]

[workspace]

[dependencies]
mylibrary = { path = "mylibrary" }

src / main.rs

extern crate mylibrary;

fn main() {
    println!("I'm using the library: {:?}", mylibrary::really_complicated_code(1, 2));
}

mylibrary / src / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

E execute:

$ cargo run
   Compiling mylibrary v0.1.0 (file:///private/tmp/the-binary/mylibrary)
   Compiling the-binary v0.1.0 (file:///private/tmp/the-binary)
    Finished dev [unoptimized + debuginfo] target(s) in 0.73 secs
     Running `target/debug/the-binary`
I'm using the library: Ok(3)

Existem dois grandes benefícios para esse esquema:

  1. O binário agora pode usar dependências que se aplicam apenas a ele. Por exemplo, você pode incluir várias caixas para melhorar a experiência do usuário, como analisadores de linha de comando ou formatação de terminal. Nada disso "infectará" a biblioteca.

  2. O espaço de trabalho evita construções redundantes de cada componente. Se rodarmos cargo buildno diretório mylibrarye the-binary, a biblioteca não será construída nas duas vezes - será compartilhada entre os dois projetos.

Shepmaster
fonte
Este parece ser um caminho muito melhor a seguir. Obviamente, faz anos desde que a pergunta foi feita, mas as pessoas ainda lutam para organizar grandes projetos. Existe uma desvantagem em usar um espaço de trabalho versus a resposta selecionada acima?
Jspies
4
@Jspies A maior desvantagem que consigo pensar é que existem algumas ferramentas que não sabem completamente como lidar com os espaços de trabalho. Eles estão meio que estranhos ao interagir com as ferramentas existentes que possuem algum tipo de conceito de "projeto". Pessoalmente, tenho uma abordagem contínua: começo com tudo main.rs, depois divido-o em módulos à medida que cresce, finalmente dividindo-o src/binquando é um pouco maior, depois movendo-me para um espaço de trabalho quando começo a reutilizar fortemente a lógica principal.
Shepmaster # 8/18
obrigado vou dar uma volta. meu projeto atual possui algumas bibliotecas desenvolvidas como parte do projeto, mas também usadas externamente.
Jspies
Ele constrói e funciona muito bem, mas cargo testparece ignorar testes de unidade em lib.rs
Stein
3
@Stein Eu acho que você quer #cargo test --all
Shepmaster 22/11
18

Você pode colocar lib.rse main.rsna pasta de fontes juntos. Não há conflito e a carga construirá as duas coisas.

Para resolver conflitos de documentação, adicione ao seu Cargo.toml:

[[bin]]
name = "main"
doc = false
DenisKolodin
fonte
3
Isso seria coberto por " Além disso, você pode simplesmente criar um src / main.rs que será usado como executável padrão ". na outra resposta, não? E o conflito de documentação é resolvido pela resposta aceita, certo? Pode ser necessário esclarecer sua resposta para mostrar por que isso é único. Não há problema em referenciar as outras respostas para desenvolver sobre elas.
Shepmaster