Rust
Strukturen
Suche…
Syntax
- struct Foo {Feld1: Typ1, Feld2: Typ2}
- let foo = foo {field1: Type1 :: new (), field2: Type2 :: new ()};
- struct Bar (Type1, Type2); // Tupeltyp
- let _ = Bar (Type1 :: new (), Type2 :: new ());
- struct Baz; // einheitlicher Typ
- sei _ = Baz;
- lass Foo {Feld1, ..} = Foo; // extrahiere Feld1 durch Mustervergleich
- sei Foo {Feld1: x, ..} = Foo; // extrahiere Feld1 als x
- let foo2 = foo {field1: Type1 :: new (), .. foo}; // aus vorhandenen erstellen
- impl Foo {fn Fiddle (& self) {}} // Instanzmethode für Foo deklarieren
- impl Foo {fn tweak (& mut self) {}} // veränderbare Instanzmethode für Foo deklarieren
- impl Foo {fn double (self) {}} // Die eigene Instanzmethode für Foo deklarieren
- impl Foo {fn new () {}} // Zugeordnete Methode für Foo deklarieren
Strukturen definieren
Strukturen in Rust werden mit dem Schlüsselwort struct
definiert. Die gebräuchlichste Strukturform besteht aus einer Reihe benannter Felder:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
Oben wird eine struct
mit drei Feldern my_bool
: my_bool
, my_num
und my_string
der Typen bool
, isize
und String
.
Eine weitere Möglichkeit zum Erstellen von struct
in Rust besteht im Erstellen einer Tupelstruktur :
struct Bar (bool, isize, String);
Dies definiert eine neue Art, Bar
, die drei unbenannte Felder hat, vom Typ bool
, isize
und String
, in dieser Reihenfolge. Dies wird als das neue Typmuster bezeichnet , da es effektiv einen neuen "Namen" für einen bestimmten Typ einführt. Dies ist jedoch leistungsfähiger als die mit dem Schlüsselwort type
erstellten Aliasnamen. Bar
ist hier ein voll funktionsfähiger Typ, dh Sie können Ihre eigenen Methoden dafür schreiben (unten).
Schließlich deklarieren Sie eine struct
ohne Felder, die als einheitliche Struktur bezeichnet wird :
struct Baz;
Dies kann nützlich sein, wenn Sie sich verspotten oder testen (wenn Sie ein Merkmal trivial implementieren möchten) oder als Markentyp. Im Allgemeinen ist es jedoch unwahrscheinlich, dass Sie auf viele einheitliche Strukturen stoßen.
Beachten Sie, dass die struct
in Rust standardmäßig alle privat sind. Sie können also nicht über Code außerhalb des Moduls angesprochen werden, das den Typ definiert. Sie können einem Feld das Schlüsselwort pub
voranstellen, um dieses Feld öffentlich zugänglich zu machen. Darüber hinaus ist die struct
ist der Typ selbst privat. Um den Typ für andere Module verfügbar zu machen, muss der struct
auch pub
vorangestellt werden:
pub struct X {
my_field: bool,
pub our_field: bool,
}
Strukturwerte erstellen und verwenden
Beachten Sie die folgenden struct
:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
struct Bar (bool, isize, String);
struct Baz;
Das Erstellen neuer Strukturwerte für diese Typen ist unkompliziert:
let foo = Foo { my_bool: true, my_num: 42, my_string: String::from("hello") };
let bar = Bar(true, 42, String::from("hello"));
let baz = Baz;
Auf Felder einer Struktur zugreifen mit .
:
assert_eq!(foo.my_bool, true);
assert_eq!(bar.0, true); // tuple structs act like tuples
Bei einer veränderlichen Bindung an eine Struktur können Felder mutiert sein:
let mut foo = foo;
foo.my_bool = false;
let mut bar = bar;
bar.0 = false;
Die Pattern-Matching-Funktionen von Rust können auch verwendet werden, um einen Blick in eine struct
:
// creates bindings mb, mn, ms with values of corresponding fields in foo
let Foo { my_bool: mb, my_num: mn, my_string: ms } = foo;
assert_eq!(mn, 42);
// .. allows you to skip fields you do not care about
let Foo { my_num: mn, .. } = foo;
assert_eq!(mn, 42);
// leave out `: variable` to bind a variable by its field name
let Foo { my_num, .. } = foo;
assert_eq!(my_num, 42);
Oder erstellen Sie eine Struktur mit einer zweiten Struktur als "Vorlage" mit der Aktualisierungssyntax von Rust:
let foo2 = Foo { my_string: String::from("world"), .. foo };
assert_eq!(foo2.my_num, 42);
Strukturmethoden
Erstellen Sie einen impl
Block, um Methoden für eine Struktur zu deklarieren (dh Funktionen, die auf der struct
"on" oder Werte dieses struct
impl
:
impl Foo {
fn fiddle(&self) {
// "self" refers to the value this method is being called on
println!("fiddling {}", self.my_string);
}
}
// ...
foo.fiddle(); // prints "fiddling hello"
&self
gibt an, dass ein unveränderlicher Verweis auf eine Instanz von struct Foo
erforderlich ist, um die fiddle
Methode aufzurufen. Wenn wir die Instanz ändern möchten (z. B. eines ihrer Felder ändern), verwenden wir stattdessen ein &mut self
(dh eine veränderbare Referenz):
impl Foo {
fn tweak(&mut self, n: isize) {
self.my_num = n;
}
}
// ...
foo.tweak(43);
assert_eq!(foo.my_num, 43);
Schließlich könnten wir auch self
als Empfänger verwenden (beachten Sie das Fehlen eines &
). Dies erfordert, dass die Instanz dem Aufrufer gehört und bewirkt, dass die Instanz beim Aufruf der Methode verschoben wird. Dies kann nützlich sein, wenn wir eine vorhandene Instanz verbrauchen, zerstören oder auf andere Weise vollständig transformieren möchten. Ein Beispiel für einen solchen Anwendungsfall ist die Bereitstellung von "Verkettungsmethoden":
impl Foo {
fn double(mut self) -> Self {
self.my_num *= 2;
self
}
}
// ...
foo.my_num = 1;
assert_eq!(foo.double().double().my_num, 4);
Beachten Sie, dass wir voran auch self
mit mut
, damit wir selbst mutieren können , bevor es wieder zurück. Der Rückgabetyp der double
bedarf auch einer Erklärung. Self
innerhalb eines impl
Blocks bezieht sich auf den Typ, für den das impl
gilt (in diesem Fall Foo
). Hierbei handelt es sich meistens um eine nützliche Abkürzung, um zu vermeiden, dass die Signatur des Typs erneut eingegeben wird. In Merkmalen kann sie jedoch verwendet werden, um auf den zugrunde liegenden Typ zu verweisen, der ein bestimmtes Merkmal implementiert.
Um eine assoziierte Methode (in anderen Sprachen allgemein als "Klassenmethode" bezeichnet) für eine struct
zu deklarieren, lassen Sie das self
Argument einfach weg. Solche Methoden werden für den struct
selbst aufgerufen, nicht für eine Instanz davon:
impl Foo {
fn new(b: bool, n: isize, s: String) -> Foo {
Foo { my_bool: b, my_num: n, my_string: s }
}
}
// ...
// :: is used to access associated members of the type
let x = Foo::new(false, 0, String::from("nil"));
assert_eq!(x.my_num, 0);
Beachten Sie, dass Strukturmethoden nur für Typen definiert werden können, die im aktuellen Modul deklariert wurden. Wie bei Feldern sind auch alle Strukturmethoden standardmäßig privat und können daher nur per Code in demselben Modul aufgerufen werden. Sie können Definitionen mit dem Schlüsselwort pub
voranstellen, um sie an anderer Stelle aufrufbar zu machen.
Generische Strukturen
Strukturen können über einen oder mehrere Typparameter generisch gemacht werden. Diese Typen sind in <>
wenn Sie sich auf den Typ beziehen:
struct Gen<T> {
x: T,
z: isize,
}
// ...
let _: Gen<bool> = Gen{x: true, z: 1};
let _: Gen<isize> = Gen{x: 42, z: 2};
let _: Gen<String> = Gen{x: String::from("hello"), z: 3};
Mehrere Typen können mit Komma angegeben werden:
struct Gen2<T, U> {
x: T,
y: U,
}
// ...
let _: Gen2<bool, isize> = Gen2{x: true, y: 42};
Die Typparameter sind Teil des Typs. Zwei Variablen desselben Basistyps, aber mit unterschiedlichen Parametern, sind nicht austauschbar:
let mut a: Gen<bool> = Gen{x: true, z: 1};
let b: Gen<isize> = Gen{x: 42, z: 2};
a = b; // this will not work, types are not the same
a.x = 42; // this will not work, the type of .x in a is bool
Wenn Sie eine Funktion schreiben möchten, die eine struct
unabhängig von ihrer Typparameterzuordnung akzeptiert, muss diese Funktion auch als generisch definiert werden:
fn hello<T>(g: Gen<T>) {
println!("{}", g.z); // valid, since g.z is always an isize
}
Aber was wäre, wenn wir eine Funktion schreiben wollten, die gx
immer drucken konnte? Wir müssen T
darauf beschränken, dass es von einem Typ ist, der angezeigt werden kann. Wir können dies mit Typgrenzen machen:
use std::fmt;
fn hello<T: fmt::Display>(g: Gen<T>) {
println!("{} {}", g.x, g.z);
}
Die hello
Funktion ist jetzt nur für Gen
Instanzen definiert, deren T
Typ fmt::Display
implementiert. Wenn wir beispielsweise versucht haben, Gen<(bool, isize)>
zu übergeben, wird der Compiler sich darüber beschweren, dass hello
für diesen Typ nicht definiert ist.
Wir können Typgrenzen auch direkt in den Typparametern der struct
um anzuzeigen, dass Sie diese struct
für bestimmte Typen struct
können:
use std::hash::Hash;
struct GenB<T: Hash> {
x: T,
}
Jede Funktion, die Zugriff auf ein GenB
weiß jetzt, dass der Typ von x
Hash
implementiert und somit .x.hash()
kann. Sie können mehrere Typgrenzen für denselben Parameter angeben, indem Sie sie mit einem +
trennen.
Wie bei Funktionen können die Typgrenzen mit dem Schlüsselwort where
nach dem <>
werden:
struct GenB<T> where T: Hash {
x: T,
}
Dies hat dieselbe semantische Bedeutung, kann jedoch die Signatur bei komplexen Schranken leichter lesbar und formatierbar machen.
Typparameter stehen auch für Instanzmethoden und zugehörige Methoden der struct
:
// note the <T> parameter for the impl as well
// this is necessary to say that all the following methods only
// exist within the context of those type parameter assignments
impl<T> Gen<T> {
fn inner(self) -> T {
self.x
}
fn new(x: T) -> Gen<T> {
Gen{x: x}
}
}
Wenn Sie Typ Grenzen auf haben Gen
‚s T
, sollten diese auch in der Art Grenzen des widerspiegeln impl
. Sie können auch die impl
Schranken impl
zu sagen, dass eine bestimmte Methode nur existiert, wenn der Typ eine bestimmte Eigenschaft erfüllt:
impl<T: Hash + fmt::Display> Gen<T> {
fn show(&self) {
println!("{}", self.x);
}
}
// ...
Gen{x: 42}.show(); // works fine
let a = Gen{x: (42, true)}; // ok, because (isize, bool): Hash
a.show(); // error: (isize, bool) does not implement fmt::Display