Deep Dive into Rust's Sized Trait und Dynamically Sized Types
Lukas Schneider
DevOps Engineer · Leapcell

Einleitung
Rust ist eine Systemprogrammiersprache, die für ihre Sicherheit und Leistung bekannt ist. In Rust ist die Bestimmung der Größe von Typen zur Kompilierzeit von entscheidender Bedeutung. Einige Typen haben jedoch keine bekannte Größe zur Kompilierzeit, was das Konzept der dynamisch dimensionierten Typen (DST) in Rust einführt. Um sicherzustellen, dass die Größe eines Typs zur Kompilierzeit bekannt sein kann, führt Rust das Sized
-Trait ein.
Dieser Artikel befasst sich eingehend mit dem Sized
-Trait in Rust, einschließlich seiner Definition, seines Zwecks, seiner Verwendung und wie es mit dynamisch dimensionierten Typen zusammenhängt.
Was ist das Sized
-Trait?
In Rust ist Sized
ein spezielles Trait, das angibt, ob die Größe eines Typs zur Kompilierzeit bekannt ist. Das Sized
-Trait ist wie folgt definiert:
pub trait Sized { // Dieses Trait hat keine Methoden; es wird verwendet, um anzugeben, dass ein Typ eine bekannte Größe hat }
Es ist wichtig zu beachten, dass alle Typen standardmäßig Sized
sind, es sei denn, sie werden explizit mit spezieller Syntax als dynamisch dimensioniert gekennzeichnet.
Beziehung zwischen dynamisch dimensionierten Typen und dem Sized
-Trait
In Rust sind dynamisch dimensionierte Typen (DSTs) spezielle Typen, deren Größe zur Kompilierzeit nicht bestimmt werden kann und zur Laufzeit bekannt sein muss. Die Hauptbeispiele für DSTs sind Referenztypen und Trait-Objekte. Das Sized
-Trait dient als Indikator dafür, ob ein Typ zur Kompilierzeit eine bekannte Größe hat.
Referenztypen und das Sized
-Trait
Referenztypen sind eine Form von dynamisch dimensionierten Typen in Rust. In Rust wird eine Referenz mit &
erstellt, um auf den Wert eines anderen Typs zu verweisen. Die Größe eines Referenztyps selbst basiert nicht auf der Größe des referenzierten Werts.
fn main() { let x = 42; let reference = &x; // Referenz auf x }
Im obigen Beispiel erstellen wir eine Variable x
und verwenden dann &
, um eine Referenz reference
auf den Wert von x
zu erstellen. Die Größe der Referenz selbst hängt nicht von der Größe des referenzierten Werts ab.
Referenztypen werden jedoch nicht als DSTs betrachtet, da die Größe einer Referenz (&T
) immer festgelegt ist – sie entspricht der Größe eines Zeigers. Dies liegt daran, dass der referenzierte Wert woanders liegt (normalerweise auf dem Stapel oder Heap) und nur die Referenz gespeichert wird.
Trait-Objekte und das Sized
-Trait
Trait-Objekte sind eine andere Art von dynamisch dimensioniertem Typ in Rust. Trait-Objekte ermöglichen es, Werte verschiedener konkreter Typen über eine gemeinsame Schnittstelle (ein Trait) zu verarbeiten. Die Größe eines Trait-Objekts ist zur Kompilierzeit nicht bekannt, da sie vom jeweiligen referenzierten Typ abhängt.
trait Shape { fn area(&self) -> f64; } struct Circle { radius: f64, } impl Shape for Circle { fn area(&self) -> f64 { self.radius * self.radius * std::f64::consts::PI } } fn main() { let circle: Circle = Circle { radius: 5.0 }; let shape: &dyn Shape = &circle; // Referenz auf den konkreten Typ über das Trait-Objekt }
Im obigen Beispiel definieren wir ein Trait Shape
und implementieren es für die Struktur Circle
. Anschließend erstellen wir ein Trait-Objekt &dyn Shape
, um auf einen Wert des konkreten Typs Circle
zu verweisen. Da die tatsächliche Größe vom konkreten Typ abhängt, ist die Größe des Trait-Objekts zur Kompilierzeit nicht bekannt.
Trait-Objekte enthalten einen versteckten Zeiger auf den konkreten Wert und verwenden ihn, um Methoden aufzurufen. Daher entspricht die Größe von &dyn Trait
immer der eines Zeigers.
Einschränkungen des Sized
-Traits
In Rust unterliegen dynamisch dimensionierte Typen (DSTs) einigen Einschränkungen, insbesondere bei der Verwendung mit Generics und Trait-Implementierungen.
Einschränkungen bei Generics
Bei der Verwendung von Generics mit DSTs müssen Sie explizit unsized Typen mithilfe der Syntax ?Sized
zulassen.
// Falsch: DST kann nicht als generischer Parameter verwendet werden fn process_data<T>(data: &[T]) { // Daten verarbeiten } fn main() { let vec_data = vec![1, 2, 3, 4, 5]; process_data(&vec_data); // Kompilierfehler: DST kann nicht als generischer Parameter verwendet werden }
Im falschen Beispiel oben haben wir versucht, das DST [T]
als Parameter in einer generischen Funktion zu verwenden, was standardmäßig nicht zulässig ist. Um DSTs zuzulassen, müssen wir den generischen Parameter mit ?Sized
kennzeichnen.
// Richtig: DST als generischer Parameter zulassen fn process_data<T: ?Sized>(data: &[T]) { // Daten verarbeiten } fn main() { let vec_data = vec![1, 2, 3, 4, 5]; process_data(&vec_data); // Korrekt: DST wird als generischer Parameter verwendet }
In diesem korrekten Beispiel ermöglicht die Verwendung von ?Sized
für den Typparameter T
, dass es sich um einen dynamisch dimensionierten Typ handelt.
Einschränkungen bei Trait-Implementierungen
Aus Sicherheitsgründen müssen Sie bei der Implementierung eines Traits für ein DST in Rust die Syntax ?Sized
verwenden, um unsized Typen zuzulassen. Dies liegt daran, dass der Compiler für Trait-Objekte die Größe des konkreten Typs zur Laufzeit und nicht zur Kompilierzeit bestimmen muss.
trait Shape { fn area(&self) -> f64; } struct Circle { radius: f64, } impl Shape for Circle { fn area(&self) -> f64 { self.radius * self.radius * std::f64::consts::PI } } // Falsch: Ein Trait kann nicht für ein DST ohne `?Sized` implementiert werden impl Shape for dyn Shape { fn area(&self) -> f64 { // Trait-Methode implementieren } } fn main() { let circle: Circle = Circle { radius: 5.0 }; let shape: &dyn Shape = &circle; shape.area(); }
Im falschen Beispiel haben wir versucht, das Trait Shape
direkt für dyn Shape
zu implementieren, was nicht zulässig ist.
// Richtig: Trait für DST mit `?Sized` implementieren impl Shape for dyn Shape + ?Sized { fn area(&self) -> f64 { // Trait-Methode implementieren } } fn main() { let circle: Circle = Circle { radius: 5.0 }; let shape: &dyn Shape = &circle; shape.area(); }
Im korrekten Beispiel wird ?Sized
verwendet, um anzugeben, dass dyn Shape
dynamisch dimensioniert sein kann, wodurch die Trait-Implementierung dafür ermöglicht wird.
Verwendung
Überprüfen, ob ein Typ das Sized
-Trait implementiert
In Rust können Sie Funktionen wie std::mem::size_of
verwenden, um abzuleiten, ob ein Typ Sized
ist.
fn main() { println!("i32 ist Sized: {}", std::mem::size_of::<i32>() == std::mem::size_of::<i32>()); println!("&i32 ist Sized: {}", std::mem::size_of::<&i32>() == std::mem::size_of::<usize>()); }
Im obigen Beispiel verwenden wir size_of
, um Typgrößen zu vergleichen. Da i32
ein Sized
-Typ ist, ist die Ausgabe true
. Der Referenztyp &i32
ist ebenfalls Sized
, da die Größe einer Referenz immer die eines Zeigers ist (normalerweise die gleiche wie usize
).
Einschränken von Generics mit dem Sized
-Trait
Sie können generische Parameter explizit einschränken, um nur Sized
-Typen zu akzeptieren.
fn process_data<T: Sized>(data: &[T]) { // Daten verarbeiten } fn main() { let vec_data = vec![1, 2, 3, 4, 5]; process_data(&vec_data); // Gültig: Nur `Sized`-Typen als generische Parameter zulässig }
Im obigen Beispiel akzeptiert die Funktion process_data
nur Typen, die das Sized
-Trait als generischen Parameter T
implementieren.
Verwenden von ?Sized
zur Unterstützung von dynamisch dimensionierten Typen
Wenn ein Trait DSTs unterstützen soll, können Sie es mit ?Sized
kennzeichnen.
trait Shape { fn area(&self) -> f64; } struct Circle { radius: f64, } impl Shape for Circle { fn area(&self) -> f64 { self.radius * self.radius * std::f64::consts::PI } } impl Shape for dyn Shape + ?Sized { fn area(&self) -> f64 { // Trait-Methode implementieren } } fn main() { let circle: Circle = Circle { radius: 5.0 }; let shape: &dyn Shape = &circle; shape.area(); }
Im obigen Beispiel haben wir ?Sized
verwendet, um anzugeben, dass dyn Shape
ein DST sein kann, wodurch es möglich ist, das Trait dafür zu implementieren.
Vergleich zwischen dynamisch dimensionierten Typen und dem Sized
-Trait
Obwohl dynamisch dimensionierte Typen (DSTs) und das Sized
-Trait beide mit dem Konzept der Typgröße zusammenhängen, haben sie unterschiedliche Bedeutungen und Zwecke.
Ein dynamisch dimensionierter Typ ist eine spezielle Art von Typ, dessen Größe zur Kompilierzeit nicht bestimmt werden kann und stattdessen zur Laufzeit basierend auf den tatsächlichen Daten aufgelöst werden muss. DSTs umfassen hauptsächlich Referenztypen und Trait-Objekte. Bei der Arbeit mit DSTs ist es wichtig, sich ihrer Einschränkungen bewusst zu sein – z. B. die Tatsache, dass sie nicht direkt instanziiert werden können und ihre Verwendung in Generics eingeschränkt ist.
Auf der anderen Seite ist das Sized
-Trait ein spezielles Trait, das verwendet wird, um anzugeben, ob die Größe eines Typs zur Kompilierzeit bekannt ist. Alle Typen in Rust sind standardmäßig Sized
, es sei denn, sie sind explizit anders gekennzeichnet. Das Sized
-Trait ist besonders nützlich in Generics und Trait-Implementierungen, um die Typsicherheit in Bezug auf Größenbeschränkungen zu gewährleisten.
Fazit
Dieser Artikel bot eine detaillierte Erläuterung und Analyse des Sized
-Traits in Rust, einschließlich seiner Definition, seines Zwecks, seiner Verwendung und seiner Beziehung zu dynamisch dimensionierten Typen.
Das Sized
-Trait spielt in Rust eine wichtige Rolle, indem es sicherstellt, dass Typgrößen zur Kompilierzeit bekannt sind. Indem Sie das Sized
-Trait verstehen und richtig verwenden, können Sie sichereren und effizienteren Code schreiben.
Wir sind Leapcell, Ihre erste Wahl für das Hosting von Rust-Projekten.
Leapcell ist die Serverlose Plattform der nächsten Generation für Webhosting, asynchrone Aufgaben und Redis:
Mehrsprachige Unterstützung
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Stellen Sie unbegrenzt Projekte kostenlos bereit
- Zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 $ unterstützt 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für verwertbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Kein Betriebsaufwand – konzentrieren Sie sich einfach auf das Erstellen.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ