Ricerca…


Sintassi

  • struct Foo {field1: Type1, field2: Type2}
  • let foo = Foo {field1: Type1 :: new (), field2: Type2 :: new ()};
  • struct Bar (Type1, Type2); // tipo di tupla
  • let _ = Bar (Type1 :: new (), Type2 :: new ());
  • struct Baz; // tipo di unità
  • let _ = Baz;
  • lasciamo Foo {field1, ..} = foo; // estrai campo1 per corrispondenza del modello
  • lasciamo Foo {field1: x, ..} = foo; // estrai campo1 come x
  • let foo2 = Foo {field1: Type1 :: new (), .. foo}; // costruisce da esistente
  • impl Foo {fn fiddle (& self) {}} // dichiara il metodo di istanza per Foo
  • impl Foo {fn tweak (& mut self) {}} // dichiara il metodo di istanza mutabile per Foo
  • impl Foo {fn double (self) {}} // dichiara di possedere il metodo di istanza per Foo
  • impl Foo {fn new () {}} // dichiara il metodo associato a Foo

Definizione delle strutture

Le strutture in Rust sono definite utilizzando la parola chiave struct . La forma più comune di struttura consiste in un insieme di campi con nome:

struct Foo {
    my_bool: bool,
    my_num: isize,
    my_string: String,
}

Quanto sopra dichiara una struct con tre campi: my_bool , my_num e my_string , rispettivamente dei tipi bool , isize e String .

Un altro modo per creare le struct in Rust è creare una struttura tuple :

struct Bar (bool, isize, String);

Questo definisce un nuovo tipo, Bar , che ha tre campi senza nome, di tipo bool , isize e String , in questo ordine. Questo è noto come pattern newtype , perché introduce effettivamente un nuovo "nome" per un particolare tipo. Tuttavia, lo fa in un modo più potente rispetto agli alias creati usando la parola chiave type ; Bar è qui un tipo completamente funzionale, il che significa che puoi scrivere i tuoi metodi (sotto).

Infine, dichiara una struct senza campi, chiamata struct unit-like :

struct Baz;

Questo può essere utile per il mocking o il testing (quando si vuole implementare banalmente un tratto) o come un tipo di marker. In generale, tuttavia, è improbabile che si imbattano in molte strutture simili a unità.

Nota che i campi struct in Rust sono tutti privati ​​di default --- cioè, non è possibile accedervi dal codice al di fuori del modulo che definisce il tipo. Puoi prefissare un campo con la parola chiave pub per renderlo pubblicamente accessibile. Inoltre, il tipo struct stesso è privato. Per rendere il tipo disponibile per altri moduli, la definizione della struct deve essere preceduta dal pub :

pub struct X {
    my_field: bool,
    pub our_field: bool,
}

Creare e usare i valori della struttura

Considera le seguenti definizioni di struct :

struct Foo {
    my_bool: bool,
    my_num: isize,
    my_string: String,
}
struct Bar (bool, isize, String);
struct Baz;

Costruire nuovi valori di struttura per questi tipi è semplice:

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;

Campi di accesso di una struct usando . :

assert_eq!(foo.my_bool, true);
assert_eq!(bar.0, true); // tuple structs act like tuples

Un legame mutabile a una struttura può avere i suoi campi mutati:

let mut foo = foo;
foo.my_bool = false;
let mut bar = bar;
bar.0 = false;

Le funzionalità di abbinamento del modello di Rust possono essere utilizzate anche per sbirciare all'interno di una 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);

Oppure crea una struct usando una seconda struct come "template" con la sintassi di aggiornamento di Rust:

let foo2 = Foo { my_string: String::from("world"), .. foo };
assert_eq!(foo2.my_num, 42);

Metodi di struttura

Per dichiarare i metodi su una struct (cioè, le funzioni che possono essere chiamate "on" sulla struct , o i valori di quel tipo di struct ), crea un blocco 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 qui indica un riferimento immutabile a un'istanza di struct Foo è necessario per invocare il metodo fiddle . Se volessimo modificare l'istanza (come cambiare uno dei suoi campi), prenderemmo invece un &mut self (cioè un riferimento mutabile):

impl Foo {
    fn tweak(&mut self, n: isize) {
        self.my_num = n;
    }
}

// ...
foo.tweak(43);
assert_eq!(foo.my_num, 43);

Infine, potremmo usare anche noi self (si noti la mancanza di un & ) come ricevitore. Ciò richiede che l'istanza sia di proprietà del chiamante e causerà lo spostamento dell'istanza quando si chiama il metodo. Questo può essere utile se desideriamo consumare, distruggere o altrimenti trasformare interamente un'istanza esistente. Un esempio di tale caso d'uso è quello di fornire metodi di "concatenamento":

impl Foo {
    fn double(mut self) -> Self {
        self.my_num *= 2;
        self
    }
}

// ...
foo.my_num = 1;
assert_eq!(foo.double().double().my_num, 4);

Nota che abbiamo anche prefisso self con mut modo che possiamo mutare te stesso prima di restituirlo di nuovo. Anche il tipo di ritorno del double metodo merita qualche spiegazione. Self all'interno di un blocco impl riferisce al tipo a cui si riferisce l' impl (in questo caso, Foo ). In questo caso, è principalmente un'utile stenografia per evitare di riscrivere la firma del tipo, ma nei tratti può essere usata per riferirsi al tipo sottostante che implementa un particolare tratto.

Dichiarare un metodo associato (comunemente indicato come "metodo di classe" in altre lingue) per una struct semplicemente lascia fuori l'argomento self . Tali metodi sono chiamati sul tipo struct stesso, non su un'istanza di esso:

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);

Si noti che i metodi di struttura possono essere definiti solo per i tipi dichiarati nel modulo corrente. Inoltre, come per i campi, tutti i metodi di struttura sono privati ​​per impostazione predefinita e possono pertanto essere richiamati solo da codice nello stesso modulo. Puoi anteporre le definizioni alla parola chiave pub per renderle richiamabili da un'altra posizione.

Strutture generiche

Le strutture possono essere rese generiche su uno o più parametri di tipo. Questi tipi sono indicati in <> quando si fa riferimento al tipo:

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};

Più tipi possono essere dati usando la virgola:

struct Gen2<T, U> {
    x: T,
    y: U,
}

// ...
let _: Gen2<bool, isize> = Gen2{x: true, y: 42};

I parametri di tipo sono una parte del tipo, quindi due variabili dello stesso tipo di base, ma con parametri diversi, non sono intercambiabili:

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

Se vuoi scrivere una funzione che accetta una struct prescindere dal tipo di assegnazione dei parametri, quella funzione dovrebbe anche essere resa generica:

fn hello<T>(g: Gen<T>) {
    println!("{}", g.z); // valid, since g.z is always an isize
}

Ma cosa succede se volessimo scrivere una funzione che potesse sempre stampare gx ? Dovremmo limitare T a essere di un tipo che può essere visualizzato. Possiamo farlo con limiti di tipo:

use std::fmt;
fn hello<T: fmt::Display>(g: Gen<T>) {
    println!("{} {}", g.x, g.z);
}

La funzione hello è ora definita solo per le istanze Gen cui tipo T implementa fmt::Display . Se abbiamo provato a passare in Gen<(bool, isize)> per esempio, il compilatore si lamenterebbe che hello non è definito per quel tipo.

Possiamo anche utilizzare i limiti di tipo direttamente sui parametri di tipo della struct per indicare che è possibile costruire solo la struct per determinati tipi:

use std::hash::Hash;
struct GenB<T: Hash> {
    x: T,
}

Qualsiasi funzione che abbia accesso a un GenB ora sa che il tipo di x implementa Hash , e quindi che possono chiamare .x.hash() . È possibile assegnare più limiti di tipi per lo stesso parametro separandoli con un + .

Come per le funzioni, i limiti di tipo possono essere posizionati dopo <> utilizzando la parola chiave where :

struct GenB<T> where T: Hash {
    x: T,
}

Questo ha lo stesso significato semantico, ma può rendere la firma più facile da leggere e formattare quando si hanno limiti complessi.

I parametri di tipo sono anche disponibili per i metodi di istanza e i metodi associati della 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}
    }
}

Se si dispone di limiti di tipo su T Gen , anche questi dovrebbero riflettersi nei limiti del tipo di impl . È inoltre possibile effettuare le impl limiti più stretto per dire che un dato metodo esiste solo se il tipo soddisfa una proprietà particolare:

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow