Rust
structuren
Zoeken…
Syntaxis
- struct Foo {veld1: Type1, veld2: Type2}
- let foo = Foo {field1: Type1 :: new (), field2: Type2 :: new ()};
- struct Bar (Type1, Type2); // tuple type
- let _ = Bar (Type1 :: new (), Type2 :: new ());
- struct Baz; // eenheidachtig type
- let _ = Baz;
- laat Foo {veld1, ..} = foo; // extraheer veld1 door patroonvergelijking
- laat Foo {veld1: x, ..} = foo; // extraheert veld1 als x
- let foo2 = Foo {field1: Type1 :: new (), .. foo}; // bouwen van bestaande
- impl Foo {fn fiddle (& self) {}} // declareer instantiemethode voor Foo
- impl Foo {fn tweak (& mut self) {}} // declareerbare instantie-methode voor Foo
- impl Foo {fn double (self) {}} // declareer de instantie van de instantie voor Foo
- impl Foo {fn new () {}} // declareer de bijbehorende methode voor Foo
Structuren definiëren
Structuren in Rust worden gedefinieerd met het sleutelwoord struct
. De meest voorkomende vorm van structuur bestaat uit een reeks benoemde velden:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
Het bovenstaande declareert een struct
met drie velden: my_bool
, my_num
en my_string
van respectievelijk de typen bool
, isize
en String
.
Een andere manier om struct
te maken in Rust is om een tuple struct te maken :
struct Bar (bool, isize, String);
Dit definieert een nieuw type, Bar
, met drie niet-genoemde velden, type bool
, isize
en String
, in die volgorde. Dit staat bekend als het newtype- patroon , omdat het effectief een nieuwe "naam" voor een bepaald type introduceert. Dit gebeurt echter op een krachtigere manier dan de aliassen die zijn gemaakt met het trefwoord type
; Bar
is hier een volledig functioneel type, wat betekent dat u er uw eigen methoden voor kunt schrijven (hieronder).
struct
ten slotte een struct
zonder velden, een eenheidachtige struct genoemd :
struct Baz;
Dit kan handig zijn voor het bespotten of testen (wanneer u een eigenschap triviaal wilt implementeren), of als een marker-type. Over het algemeen is het echter onwaarschijnlijk dat u veel unit-achtige structuren tegenkomt.
Merk op dat struct
velden in Rust standaard allemaal privé zijn, dat wil zeggen dat ze niet toegankelijk zijn vanuit code buiten de module die het type definieert. U kunt een veld voorafgaan met het trefwoord pub
om dat veld openbaar toegankelijk te maken. Bovendien is het struct
type zelf privé. Om het type beschikbaar te maken voor andere modules, moet de struct
definitie ook worden voorafgegaan door pub
:
pub struct X {
my_field: bool,
pub our_field: bool,
}
Structuurwaarden maken en gebruiken
Overweeg de volgende struct
:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
struct Bar (bool, isize, String);
struct Baz;
Het construeren van nieuwe structuurwaarden voor deze typen is eenvoudig:
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;
Toegang tot velden van een struct met .
:
assert_eq!(foo.my_bool, true);
assert_eq!(bar.0, true); // tuple structs act like tuples
Een muteerbare binding aan een struct kan zijn velden gemuteerd hebben:
let mut foo = foo;
foo.my_bool = false;
let mut bar = bar;
bar.0 = false;
De patroonafstemmingsmogelijkheden van Rust kunnen ook worden gebruikt om in een struct
te gluren:
// 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);
Of maak een struct met een tweede struct als een "sjabloon" met de update-syntaxis van Rust:
let foo2 = Foo { my_string: String::from("world"), .. foo };
assert_eq!(foo2.my_num, 42);
Structuur methoden
Om methoden op een struct aan te geven (dat wil zeggen functies die "op" de struct
kunnen worden genoemd, of waarden van dat struct
type), maakt u een impl
blok:
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
geeft hier aan dat een onveranderlijke verwijzing naar een instantie van struct Foo
nodig is om de fiddle
gebruiken. Als we de instantie wilden wijzigen (zoals het wijzigen van een van de velden), zouden we in plaats daarvan een &mut self
(dat wil zeggen een veranderlijke verwijzing):
impl Foo {
fn tweak(&mut self, n: isize) {
self.my_num = n;
}
}
// ...
foo.tweak(43);
assert_eq!(foo.my_num, 43);
Tenslotte kunnen we ook gebruik maken van self
(let op het ontbreken van een &
) als de ontvanger. Dit vereist dat het exemplaar eigendom is van de beller en dat het exemplaar wordt verplaatst wanneer de methode wordt aangeroepen. Dit kan handig zijn als we een bestaande instantie willen consumeren, vernietigen of anderszins volledig willen transformeren. Een voorbeeld van zo'n use-case is het bieden van "chaining" -methoden:
impl Foo {
fn double(mut self) -> Self {
self.my_num *= 2;
self
}
}
// ...
foo.my_num = 1;
assert_eq!(foo.double().double().my_num, 4);
Merk op dat we ook voorafgegaan door self
met mut
, zodat we kunnen zelf muteren alvorens weer terug te sturen. Het retourtype van de double
methode verdient ook enige uitleg. Self
in een impl
verwijst naar het type waarop het impl
toepassing is (in dit geval Foo
). Hier is het meestal een handig afkorting om te voorkomen dat u de handtekening van het type opnieuw hoeft te typen, maar in kenmerken kan het worden gebruikt om te verwijzen naar het onderliggende type dat een bepaalde eigenschap implementeert.
Om een bijbehorende methode (algemeen aangeduid als een "class methode" in andere talen) voor een te verklaren struct
gewoon weglaten het self
argument. Dergelijke methoden worden op het struct
type zelf aangeroepen, niet op een instantie ervan:
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);
Merk op dat structuurmethoden alleen kunnen worden gedefinieerd voor typen die in de huidige module zijn gedeclareerd. Bovendien zijn, net als bij velden, alle structuurmethoden standaard privé en kunnen ze dus alleen door code in dezelfde module worden aangeroepen. U kunt definities aan het pub
trefwoord toevoegen om ze van elders op te vragen.
Generieke structuren
Structuren kunnen generiek worden gemaakt over een of meer typeparameters. Deze types worden gegeven tussen <>
wanneer ze verwijzen naar het type:
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};
Er kunnen meerdere typen worden gegeven met komma:
struct Gen2<T, U> {
x: T,
y: U,
}
// ...
let _: Gen2<bool, isize> = Gen2{x: true, y: 42};
De typeparameters maken deel uit van het type, dus twee variabelen van hetzelfde basistype, maar met verschillende parameters, zijn niet uitwisselbaar:
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
Als u een functie wilt schrijven die een struct
accepteert, ongeacht de toewijzing van het type parameter, moet die functie ook generiek worden gemaakt:
fn hello<T>(g: Gen<T>) {
println!("{}", g.z); // valid, since g.z is always an isize
}
Maar wat als we een functie wilden schrijven die altijd gx
kon afdrukken? We zouden T
moeten beperken tot een type dat kan worden weergegeven. We kunnen dit doen met typegrenzen:
use std::fmt;
fn hello<T: fmt::Display>(g: Gen<T>) {
println!("{} {}", g.x, g.z);
}
De hello
functie is nu alleen gedefinieerd voor Gen
instanties waarvan het T
type fmt::Display
implementeert. Als we bijvoorbeeld proberen een Gen<(bool, isize)>
te geven, zou de compiler klagen dat hello
niet voor dat type is gedefinieerd.
We kunnen ook typegrenzen rechtstreeks op de typeparameters van de struct
om aan te geven dat u die struct
voor bepaalde typen kunt construeren:
use std::hash::Hash;
struct GenB<T: Hash> {
x: T,
}
Elke functie die toegang heeft tot een GenB
weet nu dat het type x
Hash
implementeert en dus .x.hash()
kan aanroepen. U kunt meerdere typegrenzen voor dezelfde parameter opgeven door ze te scheiden met een +
.
Hetzelfde als voor de functies kan het type grenzen worden geplaatst na <>
met het where
trefwoord:
struct GenB<T> where T: Hash {
x: T,
}
Dit heeft dezelfde semantische betekenis, maar kan de handtekening gemakkelijker lezen en opmaken wanneer u complexe grenzen heeft.
Type parameters zijn ook beschikbaar voor instantiemethoden en bijbehorende methoden van de 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}
}
}
Als u impl
hebt op Gen
's T
, moeten deze ook worden weerspiegeld in de impl
van het impl
. Je kunt de grenzen van de impl
strenger maken om te zeggen dat een bepaalde methode alleen bestaat als het type aan een bepaalde eigenschap voldoet:
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