Ein bisschen Rust zu Ihrem C
Die Verwendung von Rust-Code in einem C- oder C++-Projekt besteht größtenteils aus zwei Teilen.
- Erstellung einer C-kompatiblen API in Rust
- Einbindung Ihres Rust-Projekts in ein externes Build-System
Abgesehen von cargo und meson bieten die meisten Build-Systeme keine native Rust-Unterstützung. Daher fährst du höchstwahrscheinlich am besten damit, einfach cargo für die Kompilierung deines Crates und aller Abhängigkeiten zu verwenden.
Ein Projekt einrichten
Erstellen Sie wie gewohnt ein neues cargo-Projekt.
Es gibt Flags, mit denen man cargo anweisen kann, eine Systembibliothek anstelle des üblichen Rust-Ziels zu erstellen. Auf diese Weise können Sie auch einen abweichenden Namen für die Bibliothek festlegen, falls dieser sich von dem des restlichen Crates unterscheiden soll.
[lib]
name = "your_crate"
crate-type = ["cdylib"] # Erstellt eine dynamische Bibliothek
# crate-type = ["staticlib"] # Erstellt eine statische Bibliothek
Erstellung einer C-API
Da C++ über keine stabile ABI verfügt, auf die der Rust-Compiler abzielen könnte, nutzen wir C für die Interoperabilität zwischen verschiedenen Sprachen. Dies gilt auch für die Verwendung von Rust innerhalb von C- und C++-Code.
#[no_mangle]
Der Rust-Compiler verarbeitet Symbolnamen anders als Linker für nativen Code erwarten. Daher muss jeder Funktion, die Rust zur Verwendung außerhalb von Rust exportiert, mitgeteilt werden, dass der Compiler sie nicht verändern soll.
extern "C"
Standardmäßig verwendet jede in Rust geschriebene Funktion die Rust-ABI (die ebenfalls nicht stabilisiert ist). Beim Erstellen von nach außen gerichteten FFI-APIs muss der Compiler daher angewiesen werden, die System-ABI zu verwenden.
Je nach Plattform möchten Sie möglicherweise eine bestimmte ABI-Version anvisieren; diese sind hier dokumentiert.
Wenn man diese Teile zusammensetzt, erhält man eine Funktion, die ungefähr so aussieht.
#[no_mangle]
pub extern "C" fn rust_function() {
}Genau wie bei der Verwendung von C-Code in Ihrem Rust-Projekt müssen Sie nun Daten in eine Form umwandeln – und aus dieser zurück –, die der Rest der Anwendung versteht.
Verknüpfung und übergeordneter Projektkontext
Damit ist die eine Hälfte des Problems gelöst. Wie verwendet man das nun?
Das hängt stark von Ihrem Projekt bzw. Ihrem Build-System ab.
cargo erstellt – je nach Plattform und Einstellungen – eine Datei namens my_lib.so, my_lib.dll oder my_lib.a. Diese Bibliothek kann einfach von Ihrem Build-System eingebunden (gelinkt) werden.
Um jedoch eine Rust-Funktion aus C heraus aufzurufen, ist eine Header-Datei erforderlich, in der die Funktionssignaturen deklariert werden.
Für jede Funktion in Ihrer Rust-FFI-API muss eine entsprechende Deklaration im Header vorhanden sein.
#[no_mangle]
pub extern "C" fn rust_function() {}würde dann werden
void rust_function();usw.
Es gibt ein Werkzeug zur Automatisierung dieses Prozesses namens cbindgen, das Ihren Rust-Code analysiert und daraus Header-Dateien für Ihre C- und C++-Projekte generiert.
An diesem Punkt ist die Verwendung der Rust-Funktionen aus C heraus so einfach, wie den Header einzubinden und die Funktionen aufzurufen!
#include "my-rust-project.h"
rust_function();