Rust-Paket mit einer Bibliothek und einer Binärdatei?

190

Ich möchte ein Rust-Paket erstellen, das sowohl eine wiederverwendbare Bibliothek (in der der größte Teil des Programms implementiert ist) als auch eine ausführbare Datei enthält, die es verwendet.

Angenommen, ich habe keine Semantik im Rust-Modulsystem verwechselt. Wie sollte meine Cargo.tomlDatei aussehen?

Andrew Wagner
quelle

Antworten:

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
quelle
2
Danke Doug, ich werde es versuchen! Sind die Anmerkungen #! [Crate_name =] und #! [Crate_type] dann optional?
Andrew Wagner
4
Wenn Sie Cargo verwenden, sind diese Optionen nicht erforderlich, da Cargo sie als Compiler-Flags übergibt. Wenn Sie ausführen cargo build --verbose, werden sie in der rustcBefehlszeile angezeigt.
Vladimir Matveev
33
Wissen Sie, warum [[bin]]es eine Reihe von Tabellen gibt? Warum verwenden [[bin]]und nicht [bin]? Es scheint keine Dokumentation dazu zu geben.
CMCDragonkai
40
@CMCDragonkai Es ist die Toml-Formatspezifikation [[x]] ist ein Array, das einmal deserialisiert wurde. dh. Eine einzelne Kiste kann mehrere Binärdateien erzeugen, aber nur eine Bibliothek (also [lib], nicht [[lib]]). Sie können mehrere Behälterabschnitte haben. (Ich stimme zu, das sieht komisch aus, aber Toml war immer eine kontroverse Wahl).
Doug
1
Gibt es eine Möglichkeit zu verhindern, dass die Binärdatei kompiliert wird, wenn ich nur die Bibliothek möchte? Die Binärdatei hat zusätzliche Abhängigkeiten, die ich über eine Funktion namens "binär" hinzufüge. Wenn ich versuche, sie ohne diese Funktion zu kompilieren, kann sie nicht erstellt werden. Es wird beanstandet, dass die Kisten, die bin.rs zu importieren versucht, nicht gefunden werden können.
Person93
150

Sie können auch einfach Binärquellen src/binund den Rest Ihrer Quellen eingeben src. Sie können ein Beispiel in meinem Projekt sehen . Sie müssen Ihre Cargo.tomlüberhaupt nicht ändern , und jede Quelldatei wird zu einer Binärdatei mit demselben Namen kompiliert.

Die Konfiguration der anderen Antwort wird dann ersetzt durch:

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

Und führen Sie es aus:

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

Außerdem können Sie einfach eine erstellen src/main.rs, die als ausführbare defacto-Datei verwendet wird. Leider widerspricht dies dem cargo docBefehl:

Ein Paket, in dem eine Bibliothek und eine Binärdatei denselben Namen haben, kann nicht dokumentiert werden. Erwägen Sie, eines umzubenennen oder das Ziel als zu markierendoc = false

Shepmaster
quelle
13
passt gut zum konventionellen Ansatz von rust! beide Antworten zusammen und Sie haben eine große Bequemlichkeit und Flexibilität.
fliegende Schafe
9
extern crate example;ist ab Rost 2018 nicht mehr erforderlich, Sie können use example::really_complicated_code;die Funktion direkt schreiben und verwenden, ohne den Bereich zu benennen
Sassman
47

Eine alternative Lösung besteht darin, nicht zu versuchen, beide Dinge in einem Paket zusammenzufassen. Für etwas größere Projekte mit einer benutzerfreundlichen ausführbaren Datei fand ich es sehr schön, eine zu verwenden Arbeitsbereich zu verwenden

Wir erstellen ein Binärprojekt, das eine Bibliothek enthält:

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

Cargo.toml

Dies verwendet den [workspace]Schlüssel und hängt von der Bibliothek ab:

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

Und führen Sie es aus:

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

Dieses System bietet zwei große Vorteile:

  1. Die Binärdatei kann jetzt Abhängigkeiten verwenden, die nur für sie gelten. Sie können beispielsweise viele Kisten einfügen, um die Benutzererfahrung zu verbessern, z. B. Befehlszeilen-Parser oder Terminal-Formatierung. Keines davon "infiziert" die Bibliothek.

  2. Der Arbeitsbereich verhindert redundante Builds jeder Komponente. Wenn wir cargo buildsowohl im Verzeichnis mylibraryals auch im the-binaryVerzeichnis ausführen , wird die Bibliothek nicht beide Male erstellt - sie wird von beiden Projekten gemeinsam genutzt.

Shepmaster
quelle
Dies scheint ein viel besserer Weg zu sein. Offensichtlich ist es Jahre her, seit die Frage gestellt wurde, aber die Leute haben immer noch Schwierigkeiten, große Projekte zu organisieren. Gibt es einen Nachteil bei der Verwendung eines Arbeitsbereichs gegenüber der oben ausgewählten Antwort?
Jspies
4
@Jspies Der größte Nachteil, den ich mir vorstellen kann, ist, dass es einige Tools gibt, die nicht genau wissen, wie sie mit Arbeitsbereichen umgehen sollen. Sie befinden sich an einer seltsamen Stelle, wenn sie mit vorhandenen Tools interagieren, die eine Art "Projekt" -Konzept haben. Ich persönlich tendiere dazu, einen Kontinuumsansatz zu verfolgen: Ich beginne mit allem main.rs, zerlege es dann in Module, wenn es größer wird, teile src/bines schließlich auf, wenn es nur ein wenig größer ist, und gehe dann zu einem Arbeitsbereich, wenn ich anfange, die Kernlogik stark wiederzuverwenden.
Shepmaster
Danke, ich werde es versuchen. Mein aktuelles Projekt enthält einige Bibliotheken, die als Teil des Projekts entwickelt, aber auch extern verwendet werden.
Jspies
Es baut und läuft gut, cargo testscheint aber Unit-Tests in lib.rs
Stein
3
@Stein Ich denke du willstcargo test --all
Shepmaster
18

Sie können lib.rsund main.rszu Quellordner zusammenfügen. Es gibt keinen Konflikt und Fracht wird beides bauen.

Um einen Dokumentationskonflikt zu lösen, fügen Sie Folgendes hinzu Cargo.toml:

[[bin]]
name = "main"
doc = false
DenisKolodin
quelle
3
Dies wird durch " Zusätzlich können Sie einfach eine src / main.rs erstellen, die als defacto-ausführbare Datei verwendet wird " abgedeckt . in der anderen Antwort nein? Und der Dokumentationskonflikt wird durch die akzeptierte Antwort gelöst, oder? Möglicherweise müssen Sie Ihre Antwort klarstellen, um zu zeigen, warum dies einzigartig ist. Es ist in Ordnung, auf die anderen Antworten zu verweisen, um darauf aufzubauen.
Shepmaster