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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow