Rust
Struktury
Szukaj…
Składnia
- struct Foo {field1: Type1, field2: Type2}
- let foo = Foo {field1: Type1 :: new (), field2: Type2 :: new ()};
- Struktur Bar (Type1, Type2); // typ krotki
- let _ = Bar (Type1 :: new (), Type2 :: new ());
- struct Baz; // typ podobny do jednostki
- niech _ = Baz;
- niech Foo {field1, ..} = foo; // wyodrębnij pole1 przez dopasowanie wzorca
- niech Foo {field1: x, ..} = foo; // wyodrębnij pole1 jako x
- let foo2 = Foo {field1: Type1 :: new (), .. foo}; // konstrukcja z istniejącego
- impl Foo {fn fiddle (& self) {}} // deklaruj metodę instancji dla Foo
- impl Foo {fn tweak (& mut self) {}} // zadeklaruj metodę instancji mutable dla Foo
- impl Foo {fn double (self) {}} // zadeklaruj własność metody instancji dla Foo
- impl Foo {fn new () {}} // zadeklaruj powiązaną metodę dla Foo
Definiowanie struktur
Struktury w Rust są definiowane za pomocą słowa kluczowego struct
. Najpopularniejsza forma struktury składa się z zestawu nazwanych pól:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
Powyższe deklaruje struct
z trzema polami: my_bool
, my_num
i my_string
, odpowiednio typu bool
, isize
i String
.
Innym sposobem tworzenia struct
w Rust jest utworzenie krotnej struktury :
struct Bar (bool, isize, String);
Definiuje to nowy typ, Bar
, który ma trzy nienazwane pola, typu bool
, isize
i String
, w tej kolejności. Jest to znane jako wzorzec nowego typu , ponieważ skutecznie wprowadza nową „nazwę” dla określonego typu. Robi to jednak w bardziej wydajny sposób niż aliasy utworzone przy użyciu słowa kluczowego type
; Bar
jest tutaj w pełni funkcjonalnym typem, co oznacza, że możesz napisać dla niego własne metody (poniżej).
Na koniec zadeklaruj struct
bez pól, zwaną strukturą podobną do jednostki :
struct Baz;
Może to być przydatne do kpienia lub testowania (gdy chcesz w prosty sposób zaimplementować cechę) lub jako typ znacznika. Zasadniczo jednak nie jest prawdopodobne, abyś natknął się na wiele struktur podobnych do jednostek.
Zauważ, że wszystkie pola struct
w Rust są domyślnie prywatne - to znaczy, że nie można uzyskać do nich dostępu z kodu poza modułem, który definiuje typ. Możesz poprzedzić pole słowem kluczowym pub
aby to pole było publicznie dostępne. Ponadto sam typ struct
jest prywatny. Aby ten typ był dostępny dla innych modułów, definicję struct
należy również poprzedzić przedrostkiem pub
:
pub struct X {
my_field: bool,
pub our_field: bool,
}
Tworzenie i używanie wartości struktur
Rozważ następujące definicje struct
:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
struct Bar (bool, isize, String);
struct Baz;
Konstruowanie nowych wartości struktury dla tych typów jest proste:
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;
Dostęp do pól struktury za pomocą .
:
assert_eq!(foo.my_bool, true);
assert_eq!(bar.0, true); // tuple structs act like tuples
Zmienne wiązanie do struktury może mieć zmutowane pola:
let mut foo = foo;
foo.my_bool = false;
let mut bar = bar;
bar.0 = false;
Możliwości dopasowania wzorów przez Rdza można również wykorzystać do podglądania 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);
Lub utwórz strukturę, używając drugiej struktury jako „szablonu” ze składnią aktualizacji Rust:
let foo2 = Foo { my_string: String::from("world"), .. foo };
assert_eq!(foo2.my_num, 42);
Metody struktury
Aby zadeklarować metody w strukturze (tj. Funkcje, które można nazwać struct
lub wartościami tego typu struct
), utwórz blok 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
wskazuje tutaj niezmienne odwołanie do instancji struct Foo
jest konieczne do wywołania metody fiddle
. Gdybyśmy chcieli zmodyfikować instancję (na przykład zmienić jedno z jej pól), zamiast tego wzięlibyśmy &mut self
(tj. Zmienne odwołanie):
impl Foo {
fn tweak(&mut self, n: isize) {
self.my_num = n;
}
}
// ...
foo.tweak(43);
assert_eq!(foo.my_num, 43);
Wreszcie, możemy również użyć self
(zauważ brak &
) jako odbiornika. Wymaga to, aby instancja była własnością wywołującego, i spowoduje przeniesienie instancji podczas wywoływania metody. Może to być przydatne, jeśli chcemy skonsumować, zniszczyć lub w inny sposób całkowicie przekształcić istniejącą instancję. Jednym z przykładów takiego przypadku użycia jest zapewnienie metod „łączenia”:
impl Foo {
fn double(mut self) -> Self {
self.my_num *= 2;
self
}
}
// ...
foo.my_num = 1;
assert_eq!(foo.double().double().my_num, 4);
Zauważ, że poprzedziliśmy także self
mut
, abyśmy mogli mutować self przed zwróceniem go ponownie. Typ zwrotu metody double
również wymaga wyjaśnienia. Self
wewnątrz bloku impl
odnosi się do typu, którego dotyczy impl
(w tym przypadku Foo
). W tym przypadku jest to przede wszystkim użyteczny skrót, aby uniknąć ponownego wpisywania podpisu typu, ale w przypadku cech można go użyć w odniesieniu do typu podstawowego, który implementuje określoną cechę.
Aby zadeklarować powiązaną metodę (zwaną w innych językach „metodą klasową”) dla struct
po prostu pomiń argument self
. Takie metody są wywoływane w samym typie struct
, a nie w jego instancji:
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);
Zauważ, że metody struktury można zdefiniować tylko dla typów zadeklarowanych w bieżącym module. Ponadto, podobnie jak w przypadku pól, wszystkie metody struktury są domyślnie prywatne i dlatego można je wywoływać tylko kodem w tym samym module. Definicje można poprzedzać słowem kluczowym pub
aby można je było wywoływać z dowolnego miejsca.
Ogólne struktury
Struktury mogą być generyczne na podstawie jednego lub więcej parametrów typu. Te typy podano w <>
odniesieniu do typu:
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};
Za pomocą przecinka można podać wiele typów:
struct Gen2<T, U> {
x: T,
y: U,
}
// ...
let _: Gen2<bool, isize> = Gen2{x: true, y: 42};
Parametry typu są częścią typu, więc dwie zmienne tego samego typu podstawowego, ale o różnych parametrach, nie są zamienne:
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
Jeśli chcesz napisać funkcję, która akceptuje struct
bez względu na przypisanie parametru typu, funkcja ta musiałaby być również ogólna:
fn hello<T>(g: Gen<T>) {
println!("{}", g.z); // valid, since g.z is always an isize
}
Ale co jeśli chcielibyśmy napisać funkcję, która zawsze może wypisywać gx
? Musielibyśmy ograniczyć T
aby był typu, który można wyświetlić. Możemy to zrobić za pomocą granic typów:
use std::fmt;
fn hello<T: fmt::Display>(g: Gen<T>) {
println!("{} {}", g.x, g.z);
}
Funkcja hello
jest teraz zdefiniowana tylko dla instancji Gen
których typ T
implementuje fmt::Display
. Gdybyśmy próbowali przekazać na przykład Gen<(bool, isize)>
, kompilator Gen<(bool, isize)>
że dla tego typu nie zdefiniowano hello
.
Możemy również użyć granic typów bezpośrednio w parametrach typu struct
aby wskazać, że możesz zbudować tę struct
dla niektórych typów:
use std::hash::Hash;
struct GenB<T: Hash> {
x: T,
}
Każda funkcja, która ma dostęp do GenB
wie teraz, że typ x
implementuje Hash
, a zatem może wywoływać .x.hash()
. Można podać wiele granic typów dla tego samego parametru, oddzielając je znakiem +
.
Podobnie jak w przypadku funkcji, granice typów można umieszczać po <>
za pomocą słowa kluczowego where
:
struct GenB<T> where T: Hash {
x: T,
}
Ma to to samo znaczenie semantyczne, ale może ułatwić czytanie i formatowanie podpisu, gdy masz złożone granice.
Parametry typu są również dostępne dla metod instancji i powiązanych metod 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}
}
}
Jeśli masz granice typu na T
Gen
, powinny one również być odzwierciedlone w granicach typu impl
. Możesz także impl
granice impl
aby powiedzieć, że dana metoda istnieje tylko wtedy, gdy typ spełnia określoną właściwość:
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