Ferris Talk #10: Constant Fun mit Rust – const fn

In Version 1.61 hat Rust Tricks in Sachen konstanter Code-Evaluierung gelernt. Diese Ausgabe der Kolumne zeigt, was const in Rust kann und was es Neues gibt.

In Pocket speichern vorlesen Druckansicht
Ferris Talk – Neuigkeiten zu Rust. Eine Heise-Kolumne von Rainer Stropek und Stefan Baumgartner für Rustaceans
Lesezeit: 9 Min.
Von
  • Rainer Stropek
Inhaltsverzeichnis

Egal, von welcher Programmiersprache man kommt, Konstanten gibt es praktisch überall. Rust wäre nicht seit Jahren eine der beliebtesten Programmiersprachen laut Stack Overflow Survey, wenn sie nicht auch im Bereich der Konstanten etwas zu bieten hätte, was man in vielen anderen Sprachen nicht findet. In der kürzlich erschienenen Version 1.61 hat Rust einige Tricks in Sachen Konstanten und konstanter Evaluierung von Code dazugelernt.

Die in nächster Zeit geplanten Versionen werden laut aktuell verfügbarer Nightly weitere Neuerungen in dem Bereich bringen. Das nehmen wir in der Rust-Kolumne zum Anlass, in dieser Ausgabe des Ferris Talk const und const fn in Rust vorzustellen und anhand von Beispielen zu zeigen, was es Neues gibt.

Ferris Talks – die Kolumne für Rustaceans

In diesem Artikel stehen die Codebeispiele im Mittelpunkt. Damit Interessierte den gezeigten Code ausprobieren und vor allem damit experimentieren können, ist bei jedem Beispiel ein Link angegeben, der den Code im Rust Playground öffnet. Dort lässt er sich ausführen und ändern, selbst wenn Rust noch nicht lokal installiert sein sollte.

Starten wir mit den Grundlagen, die in Rust ähnlich funktionieren wie in anderen Programmiersprachen. Folgender Code legt Konstanten mit verschiedenen Typen an (siehe Rust Playground, Sample 1). Die Zuweisung des Wertes zur Konstanten ANSWER ist besonders zu beachten. Der Wert lässt sich durch einen konstanten Ausdruck ermitteln. In einem solchen Kontext steht nicht alles zur Verfügung, was Rust zur Laufzeit an Ausdrücken beherrscht. Die konkreten Einschränkungen sind in der Rust-Dokumentation beschrieben.

// A simple constant value
const NEARLY_THE_ANSWER: i32 = 41;

// A constant expression that is evaluated at compile time.
// Note: Not everything is allowed in a constant expression. See
// https://doc.rust-lang.org/reference/const_eval.html#constant-expressions
// for details
const ANSWER: i32 = NEARLY_THE_ANSWER + 1;

// A simple constant string (because of static lifetime elision,
// we don't need 'static)
const TEXT: &str = "the quick brown fox jumps over the lazy dog!";

// A constant array
const NUMBERS: [i32; 5] = [1, 2, 3, 4, 5];

fn main() {
    // Constants are inlined at compile time wherever they are used.
    println!("The answer is {ANSWER}");
    println!("{TEXT}");
    println!("Numbers: {:?}", NUMBERS);

    // Constants can be declared in any scope, not just global.
    const VERSION: &str = "1.2.3";
    println!("Version: {}", VERSION);
}

Konstanten in Rust sind nicht auf Basisdatentypen wie Zahlen oder Zeichenketten beschränkt. Auch Strukturen können const sein, wie das folgende Codebeispiel zeigt (siehe auch Rust Playground, Sample 2).

// Constants in Rust don't need to be basic data types.
// Structs can also be constant.
#[derive(Debug)]
struct Customer<'a> {
    name: &'a str,
    age: i32,
}
const CUSTOMER: Customer = Customer {
    name: "John",
    age: 42,
};

fn main() {
    println!(
        "Customer {} is of age {} ({:?})",
        CUSTOMER.name, CUSTOMER.age, CUSTOMER
    );

    // Note that if you modify a const item, a new temporary
    // item is created. The original const item is not modified.
    CUSTOMER.age += 1;
}

In Rust können Konstanten auf die Adressen anderer Konstanten verweisen. Das folgende Beispiel demonstriert das, indem es eine Struktur NamedNumbers definiert und im Rahmen einer Konstante verwendet (siehe Rust Playground, Sample 3). Die Konstante verweist auf andere Konstanten. Zu erwähnen ist dabei, dass beim Verwenden der Struktur im Rahmen der Konstantendeklaration die static Lifetime nicht explizit anzugeben ist, Rust ergänzt sie im Hintergrund automatisch.

const TEXT: &str = "the quick brown fox jumps over the lazy dog!";
const NUMBERS: [i32; 5] = [1, 2, 3, 4, 5];

// A constant struct.
// Note that constants may refer to the address of other constants.
// The lifetime defaults to 'static (elided).
struct NamedNumbers<'a> {
    name: &'a str,
    numbers: &'a [i32; 5],
}
const NAMED_NUMBERS: NamedNumbers = NamedNumbers {
    name: TEXT,
    numbers: &NUMBERS,
};

fn main() {
    println!("{TEXT:p}\n{:p}", NAMED_NUMBERS.name);
    println!("{}", NAMED_NUMBERS.numbers.iter().sum::<i32>());
}

Die nächste Besonderheit von Rust ist, dass Konstanten mit Typen assoziiert werden und sogar Teil eines Traits sein können. Im folgenden Beispiel verlangt der Trait HasNumbers, dass zwei Konstanten existieren (siehe Rust Playground, Sample 4). Für eine davon ist ein überschreibbarer Standardwert vorzugeben.

Empfohlener redaktioneller Inhalt

Mit Ihrer Zustimmmung wird hier ein externes YouTube-Video (Google Ireland Limited) geladen.

Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit können personenbezogene Daten an Drittplattformen (Google Ireland Limited) übermittelt werden. Mehr dazu in unserer Datenschutzerklärung.

// Constants in traits
trait HasNumbers {
    // Note that we have a constant here without a value.
    const NUMBERS: [i32; 5];

    // Constants in traits can have default values
    const LAST_NUMBER: i32 = 5;
}
struct IHaveNumbers {}
impl HasNumbers for IHaveNumbers {
    // This is an *associated constant*, as it is associated with a type.
    const NUMBERS: [i32; 5] = [1, 2, 3, 4, IHaveNumbers::LAST_NUMBER];
}
struct IHaveOtherNumbers {}
impl HasNumbers for IHaveOtherNumbers {
    // Here we override the default value of the trait.
    const LAST_NUMBER: i32 = 6;
    const NUMBERS: [i32; 5] = [1, 2, 3, 4, IHaveOtherNumbers::LAST_NUMBER];
}

fn main() {
    println!("{:?}", IHaveNumbers::NUMBERS);
    println!("{:?}", IHaveOtherNumbers::NUMBERS);
}

Jetzt verlassen wir denjenigen Bereich der Konstanten, der in anderen Programmiersprachen ähnlich ist, und sprechen über weiterführende Fähigkeiten von Rust. Als Erstes geht es um konstante Ausdrücke mit Destruktoren. Ja, richtig gehört: Konstante Ausdrücken können in Rust Destruktoren haben, die an der entsprechenden Stelle im Programm ausgeführt werden, wenn die Konstante verwendet wird. Etwas anders ist das bei statischen Variablen. Ihr Destruktor wird nicht aufgerufen, wenn das Programm bzw. der Thread beendet wird. Das folgende Listing (siehe auch Rust Playground, Sample 5) demonstriert dieses Verhalten.

// Constants can have destructors
struct WillSayGoodbye<'a>(&'a str);
impl<'a> Drop for WillSayGoodbye<'a> {
    fn drop(&mut self) {
        println!("{}", self.0);
    }
}
// Note that destructor on statics will not run on program/thread exit.
static _GOODBYE_IN_ENGLISH: WillSayGoodbye = WillSayGoodbye("Goodbye");
const GOODBYE_IN_GERMAN: WillSayGoodbye = WillSayGoodbye("Auf Wiedersehen");

fn main() {
    {
        let _goodbye_sayer = GOODBYE_IN_GERMAN;
        // Destructor will run at appropriate point when const is used. Therefore,
        // this code will print "Auf Wiedersehen" on stdout when var goes out of scope.    }
}