Package Rust avec à la fois une bibliothèque et un binaire?

190

Je voudrais créer un package Rust contenant à la fois une bibliothèque réutilisable (où la plupart du programme est implémenté) et un exécutable qui l'utilise.

En supposant que je n'ai confondu aucune sémantique dans le système de modules Rust, à quoi mon Cargo.tomlfichier devrait-il ressembler?

Andrew Wagner
la source

Réponses:

206
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
la source
2
Merci Doug, je vais l'essayer! Les annotations #! [Crate_name =] et #! [Crate_type] sont-elles alors facultatives?
Andrew Wagner
4
Lorsque vous utilisez Cargo, ces options sont inutiles car Cargo les transmet en tant qu'indicateurs du compilateur. Si vous exécutez cargo build --verbose, vous les verrez en rustcligne de commande.
Vladimir Matveev
33
Savez-vous pourquoi [[bin]]un tableau de tableaux? Pourquoi utiliser [[bin]]et non [bin]? Il ne semble pas y avoir de documentation à ce sujet.
CMCDragonkai
40
@CMCDragonkai C'est la spécification du format toml [[x]] est un tableau une fois désérialisé; c'est à dire. une seule caisse peut produire plusieurs binaires, mais une seule bibliothèque (donc [lib], pas [[lib]]). Vous pouvez avoir plusieurs sections de bac. (Je suis d'accord, cela a l'air bizarre, mais Toml a toujours été un choix controversé).
Doug
1
Existe-t-il un moyen de l'empêcher de compiler le binaire alors que je ne veux que la lib? Le binaire a des dépendances supplémentaires que j'ajoute via une fonctionnalité appelée "binaire", lorsque j'essaye de le compiler sans cette fonctionnalité, il ne parvient pas à se construire. Il se plaint de ne pas trouver les caisses que bin.rs essaie d'importer.
Person93
151

Vous pouvez également simplement insérer des sources binaires src/binet le reste de vos sources src. Vous pouvez voir un exemple dans mon projet . Vous n'avez pas du tout besoin de modifier votre Cargo.toml, et chaque fichier source sera compilé dans un binaire du même nom.

La configuration de l'autre réponse est alors remplacée par:

$ 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));
}

Et exécutez-le:

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

De plus, vous pouvez simplement créer un src/main.rsfichier qui sera utilisé comme exécutable de facto. Malheureusement, cela entre en conflit avec la cargo doccommande:

Impossible de documenter un package dans lequel une bibliothèque et un binaire ont le même nom. Envisagez d'en renommer un ou de marquer la cible commedoc = false

Shepmaster
la source
13
correspond bien à l'approche convention-sur-configuration de rust! les deux réponses ensemble et vous avez une grande commodité et flexibilité.
mouton volant le
9
extern crate example;n'est pas nécessaire à partir de rust 2018, vous pouvez directement écrire use example::really_complicated_code;et utiliser la fonction sans nommer la portée
sassman
47

Une autre solution consiste à ne pas essayer de regrouper les deux choses dans un seul paquet. Pour les projets légèrement plus grands avec un exécutable convivial, j'ai trouvé très agréable d'utiliser un espace de travail

Nous créons un projet binaire qui comprend une bibliothèque à l'intérieur:

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

Cargo.toml

Cela utilise la [workspace]clé et dépend de la bibliothèque:

[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)
}

Et exécutez-le:

$ 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)

Il y a deux grands avantages à ce programme:

  1. Le binaire peut désormais utiliser des dépendances qui ne s'appliquent qu'à lui. Par exemple, vous pouvez inclure de nombreuses caisses pour améliorer l'expérience utilisateur, telles que des analyseurs de ligne de commande ou le formatage du terminal. Aucun de ceux-ci ne «infectera» la bibliothèque.

  2. L'espace de travail empêche les versions redondantes de chaque composant. Si nous exécutons cargo buildà la fois le répertoire mylibraryet the-binary, la bibliothèque ne sera pas créée les deux fois - elle est partagée entre les deux projets.

Shepmaster
la source
Cela semble être une bien meilleure façon de procéder. Évidemment, cela fait des années que la question n'a pas été posée mais les gens ont encore du mal à organiser de grands projets. Y a-t-il un inconvénient à utiliser un espace de travail par rapport à la réponse sélectionnée ci-dessus?
Jspies
4
@Jspies, le plus gros inconvénient auquel je puisse penser, c'est qu'il existe des outils qui ne savent pas vraiment comment gérer les espaces de travail. Ils sont en quelque sorte dans un endroit étrange lorsqu'ils interagissent avec des outils existants qui ont une sorte de concept de «projet». Personnellement, j'ai tendance à adopter une approche continue: je commence par tout main.rsdedans, puis je le décompose en modules au fur et à src/binmesure qu'il s'agrandit, puis je me divise quand il est juste un peu plus grand, puis je passe à un espace de travail lorsque je commence à réutiliser massivement la logique de base.
Shepmaster
merci je vais lui donner un tour. mon projet actuel a quelques bibliothèques qui sont développées dans le cadre du projet mais également utilisées en externe.
Jspies
Il se construit et fonctionne cargo testcorrectement , mais semble ignorer les tests unitaires dans lib.rs
Stein
3
@Stein je pense que tu veuxcargo test --all
Shepmaster
18

Vous pouvez mettre lib.rset main.rsvers le dossier sources ensemble. Il n'y a pas de conflit et la cargaison construira les deux choses.

Pour résoudre un conflit de documentaion, ajoutez à votre Cargo.toml:

[[bin]]
name = "main"
doc = false
DenisKolodin
la source
3
Cela serait couvert par " De plus, vous pouvez simplement créer un src / main.rs qui sera utilisé comme exécutable de facto ". dans l'autre réponse, non? Et le conflit de documentation est résolu par la réponse acceptée, non? Vous devrez peut-être clarifier votre réponse pour montrer pourquoi cela est unique. Il est normal de faire référence aux autres réponses pour en tirer parti.
Shepmaster