Mit Makros können Sie Code schreiben, der anderen Code schreibt. Informieren Sie sich über die seltsame und mächtige Welt der Metaprogrammierung.

Die Codegenerierung ist eine Funktion, die Sie in den meisten modernen Programmiersprachen finden. Es kann Ihnen helfen, Boilerplate-Code und Codeduplizierung zu reduzieren, domänenspezifische Sprachen (DSLs) zu definieren und neue Syntax zu implementieren.

Rust bietet ein leistungsstarkes Makrosystem, mit dem Sie zur Kompilierzeit Code für anspruchsvollere Programmierungen generieren können.

Einführung in Rust-Makros

Makros sind eine Art Metaprogrammierung, die Sie nutzen können, um Code zu schreiben, der Code schreibt. In Rust ist ein Makro ein Stück Code, das zur Kompilierzeit anderen Code generiert.

Rust-Makros sind eine leistungsstarke Funktion, mit der Sie Code schreiben können, der zur Kompilierzeit anderen Code generiert, um sich wiederholende Aufgaben zu automatisieren. Die Makros von Rust tragen dazu bei, die Code-Duplizierung zu reduzieren und die Wartbarkeit und Lesbarkeit des Codes zu verbessern.

Sie können Makros verwenden, um alles zu generieren, von einfachen Codeschnipseln bis hin zu Bibliotheken und Frameworks. Makros unterscheiden sich von Rost funktioniert weil sie zur Laufzeit mit Code und nicht mit Daten arbeiten.

Makros in Rust definieren

Sie definieren Makros mit der Makro_Regeln! Makro. Der Makro_Regeln! Makro nimmt ein Muster und eine Vorlage als Eingabe. Rust vergleicht das Muster mit dem Eingabecode und verwendet die Vorlage, um den Ausgabecode zu generieren.

So können Sie Makros in Rust definieren:

Makro_Regeln! sag Hallo {
() => {
drucken!("Hallo Welt!");
};
}

fnhauptsächlich() {
sag Hallo!();
}

Der Code definiert a sag Hallo Makro, das Code zum Drucken von „Hello, world!“ generiert. Der Code stimmt mit dem überein () Syntax gegen eine leere Eingabe und die drucken! Makro generiert den Ausgabecode.

Hier ist das Ergebnis der Ausführung des Makros in der hauptsächlich Funktion:

Makros können Eingabeargumente für den generierten Code annehmen. Hier ist ein Makro, das ein einzelnes Argument akzeptiert und Code zum Drucken einer Nachricht generiert:

Makro_Regeln! say_message {
($Nachricht: Ausdruck) => {
drucken!("{}", $nachricht);
};
}

Der sag_nachricht Makro nimmt die $Nachricht Argument und generiert Code zum Drucken des Arguments mit dem drucken! Makro. Der Ausdruck syntax vergleicht das Argument mit jedem Rust-Ausdruck.

Arten von Rust-Makros

Rust bietet drei Arten von Makros. Jeder der Makrotypen dient bestimmten Zwecken und hat seine Syntax und Einschränkungen.

Prozedurale Makros

Prozedurale Makros gelten als der leistungsstärkste und vielseitigste Typ. Mit prozeduralen Makros können Sie eine benutzerdefinierte Syntax definieren, die gleichzeitig Rust-Code generiert. Sie können prozedurale Makros verwenden, um benutzerdefinierte Ableitungsmakros, benutzerdefinierte attributähnliche Makros und benutzerdefinierte funktionsähnliche Makros zu erstellen.

Sie verwenden benutzerdefinierte Ableitungsmakros, um Strukturen und Aufzählungsmerkmale automatisch zu implementieren. Beliebte Pakete wie Serde verwenden ein benutzerdefiniertes Ableitungsmakro, um Serialisierungs- und Deserialisierungscode für Rust-Datenstrukturen zu generieren.

Benutzerdefinierte attributähnliche Makros sind praktisch, um benutzerdefinierte Anmerkungen zu Rust-Code hinzuzufügen. Das Rocket-Webframework verwendet ein benutzerdefiniertes attributähnliches Makro, um Routen präzise und lesbar zu definieren.

Sie können benutzerdefinierte funktionsähnliche Makros verwenden, um neue Rust-Ausdrücke oder -Anweisungen zu definieren. Die Lazy_static-Kiste verwendet ein benutzerdefiniertes funktionsähnliches Makro, um die zu definieren faul initialisiert statische Variablen.

So können Sie ein prozedurales Makro definieren, das ein benutzerdefiniertes Ableitungsmakro definiert:

verwenden proc_macro:: TokenStream;
verwenden Zitat:: Zitat;
verwenden syn::{DeriveInput, parse_macro_input};

Der verwenden Direktiven importieren notwendige Crates und Typen zum Schreiben eines prozeduralen Makros von Rust.

#[proc_macro_derive (MyTrait)]
Kneipefnmein_ableitungsmakro(Eingabe: TokenStream) -> TokenStream {
lassen ast = parse_macro_input!(eingabe als AbleitungEingabe);
lassen name = &ast.ident;

lassen gen = Zitat! {
impl Mein Merkmal für #Name {
// Implementierung hier
}
};

gen.into()
}

Das Programm definiert ein prozedurales Makro, das die Implementierung einer Eigenschaft für eine Struktur oder Aufzählung generiert. Das Programm ruft das Makro mit dem Namen auf Mein Merkmal im Attribut derive der Struktur oder Aufzählung. Das Makro dauert a TokenStream Objekt als Eingabe, das den Code enthält, der mit dem in einen abstrakten Syntaxbaum (AST) geparst wird parse_makro_eingabe! Makro.

Der Name Variable ist die abgeleitete Struktur oder Aufzählungskennung, die zitieren! Das Makro generiert einen neuen AST, der die Implementierung von darstellt Mein Merkmal für den Typ, der schließlich als zurückgegeben wird TokenStream mit dem hinein Methode.

Um das Makro zu verwenden, müssen Sie das Makro aus dem Modul importieren, in dem Sie es deklariert haben:

// Angenommen, Sie haben das Makro in einem my_macro_module-Modul deklariert

verwenden my_macro_module:: my_derive_macro;

Beim Deklarieren der Struktur oder Aufzählung, die das Makro verwendet, fügen Sie die hinzu #[ableiten (MyTrait)] -Attribut an den Anfang der Deklaration.

#[ableiten (MyTrait)]
StrukturMeineStruktur {
// Felder hier
}

Die Struct-Deklaration mit dem Attribut wird zu einer Implementierung von erweitert Mein Merkmal Merkmal für die Struktur:

impl Mein Merkmal für MeineStruktur {
// Implementierung hier
}

Die Implementierung ermöglicht die Verwendung von Methoden in der Mein Merkmal Eigenschaft an MeineStruktur Instanzen.

Attributmakros

Attributmakros sind Makros, die Sie auf Rust-Elemente wie Strukturen, Aufzählungen, Funktionen und Module anwenden können. Attributmakros haben die Form eines Attributs gefolgt von einer Liste von Argumenten. Das Makro analysiert das Argument, um Rust-Code zu generieren.

Sie verwenden Attributmakros, um Ihrem Code benutzerdefinierte Verhaltensweisen und Anmerkungen hinzuzufügen.

Hier ist ein Attributmakro, das einer Rust-Struktur ein benutzerdefiniertes Attribut hinzufügt:

// Module für die Makrodefinition importieren
verwenden proc_macro:: TokenStream;
verwenden Zitat:: Zitat;
verwenden syn::{parse_macro_input, DeriveInput, AttributeArgs};

#[proc_macro_attribute]
Kneipefnmein_attribute_makro(attr: TokenStream, item: TokenStream) -> TokenStream {
lassen args = parse_macro_input!(attr als AttributeArgs);
lassen Eingabe = parse_macro_input!(item als AbleitungEingabe);
lassen name = &input.ident;

lassen gen = Zitat! {
#Eingang
impl #Name {
// benutzerdefiniertes Verhalten hier
}
};

gen.into()
}

Das Makro nimmt eine Liste von Argumenten und eine Strukturdefinition und generiert eine modifizierte Struktur mit dem definierten benutzerdefinierten Verhalten.

Das Makro akzeptiert zwei Argumente als Eingabe: das auf das Makro angewendete Attribut (parsed with the parse_makro_eingabe! Makro) und das Element (parsed with the parse_makro_eingabe! Makro). Das Makro verwendet die zitieren! Makro zum Generieren des Codes, einschließlich des ursprünglichen Eingabeelements und eines zusätzlichen impl Block, der das benutzerdefinierte Verhalten definiert.

Schließlich gibt die Funktion den generierten Code als zurück TokenStream mit dem hinein() Methode.

Makroregeln

Makroregeln sind die einfachste und flexibelste Art von Makros. Mit Makroregeln können Sie benutzerdefinierte Syntax definieren, die zur Kompilierzeit zu Rust-Code erweitert wird. Makroregeln definieren benutzerdefinierte Makros, die mit allen Rust-Ausdrücken oder -Anweisungen übereinstimmen.

Sie verwenden Makroregeln, um Boilerplate-Code zu generieren, um Details auf niedriger Ebene zu abstrahieren.

So können Sie Makroregeln in Ihren Rust-Programmen definieren und verwenden:

Makro_Regeln! make_vector {
( $( $x: expr ),* ) => {
{
lassenmut v = Vek::neu();
$(
v.push($x);
)*
v
}
};
}

fnhauptsächlich() {
lassen v = make_vector![1, 2, 3];
drucken!("{:?}", v); // gibt "[1, 2, 3]" aus
}

Das Programm definiert a make_vector! ein Makro, das einen neuen Vektor aus einer Liste von durch Kommas getrennten Ausdrücken in der erstellt hauptsächlich Funktion.

Innerhalb des Makros stimmt die Musterdefinition mit den an das Makro übergebenen Argumenten überein. Der $( $x: expr ),* Syntax stimmt mit allen durch Kommas getrennten Ausdrücken überein, die als gekennzeichnet sind $x.

Der $( ) Syntax im Erweiterungscode durchläuft jeden Ausdruck in der Liste der Argumente, die danach an das Makro übergeben werden die schließende Klammer, die angibt, dass die Iterationen fortgesetzt werden sollen, bis das Makro alle verarbeitet Ausdrücke.

Organisieren Sie Ihre Rust-Projekte effizient

Rust-Makros verbessern die Codeorganisation, indem sie es Ihnen ermöglichen, wiederverwendbare Codemuster und Abstraktionen zu definieren. Makros können Ihnen helfen, prägnanteren, aussagekräftigeren Code ohne Duplizierungen über verschiedene Projektteile hinweg zu schreiben.

Außerdem können Sie Rust-Programme in Crates und Module organisieren, um den Code besser zu organisieren, wiederverwendbar zu machen und mit anderen Crates und Modulen zusammenzuarbeiten.