Rust
Les structures
Recherche…
Syntaxe
- struct Foo {field1: Type1, field2: Type2}
- let foo = Foo {field1: Type1 :: new (), field2: Type2 :: new ()};
- struct Bar (Type1, Type2); // type de tuple
- let _ = Bar (Type1 :: new (), Type2 :: new ());
- struct Baz; // type d'unité
- let _ = Baz;
- let Foo {field1, ..} = foo; // extrait le champ1 par correspondance de modèle
- let Foo {field1: x, ..} = foo; // extraire field1 comme x
- laisser foo2 = Foo {field1: Type1 :: new (), .. foo}; // construit à partir de l'existant
- implémenter Foo {fn fiddle (& self) {}} // déclarer la méthode d'instance pour Foo
- impliquer Foo {fn tweak (& mut self) {}} // déclarer la méthode d'instance mutable pour Foo
- impliquer Foo {fn double (self) {}} // déclare la méthode d'instance propriétaire pour Foo
- impliquer Foo {fn new () {}} // déclare la méthode associée pour Foo
Définir des structures
Les structures de Rust sont définies à l'aide du mot struct
clé struct
. La forme de structure la plus courante consiste en un ensemble de champs nommés:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
Le ci-dessus déclare une struct
avec trois champs: my_bool
, my_num
et my_string
, respectivement des types bool
, isize
et String
.
Une autre façon de créer des struct
dans Rust consiste à créer une structure de tuple :
struct Bar (bool, isize, String);
Ceci définit un nouveau type, Bar
, qui comporte trois champs sans nom, de type bool
, isize
et String
, dans cet ordre. Ceci est connu comme le modèle newtype , car il introduit effectivement un nouveau "nom" pour un type particulier. Cependant, il le fait d'une manière plus puissante que les alias créés à l'aide du mot-clé type
; Bar
est ici un type entièrement fonctionnel, ce qui signifie que vous pouvez écrire vos propres méthodes (ci-dessous).
Enfin, déclarez une struct
sans champs, appelée structure unitaire :
struct Baz;
Cela peut être utile pour se moquer ou tester (lorsque vous souhaitez implémenter un trait de manière triviale) ou comme type de marqueur. En général, cependant, il est peu probable que vous rencontriez de nombreuses structures semblables à des unités.
Notez que les champs de struct
dans Rust sont tous privés par défaut - c'est-à-dire qu'ils ne sont pas accessibles depuis le code en dehors du module qui définit le type. Vous pouvez préfixer un champ avec le mot-clé pub
pour rendre ce champ accessible au public. De plus, le type de struct
lui - même est privé. Pour que le type soit disponible pour les autres modules, la définition de la struct
doit également être précédée de pub
:
pub struct X {
my_field: bool,
pub our_field: bool,
}
Créer et utiliser des valeurs de structure
Considérons les définitions de struct
suivantes:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
struct Bar (bool, isize, String);
struct Baz;
La construction de nouvelles valeurs de structure pour ces types est simple:
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;
Champs d'accès d'une structure utilisant .
:
assert_eq!(foo.my_bool, true);
assert_eq!(bar.0, true); // tuple structs act like tuples
Une liaison mutable à une structure peut avoir ses champs mutés:
let mut foo = foo;
foo.my_bool = false;
let mut bar = bar;
bar.0 = false;
Les fonctionnalités de corrélation de Rust peuvent également être utilisées pour visualiser une 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);
Ou créez une structure en utilisant une deuxième structure en tant que "modèle" avec la syntaxe de mise à jour de Rust:
let foo2 = Foo { my_string: String::from("world"), .. foo };
assert_eq!(foo2.my_num, 42);
Méthodes de structure
Pour déclarer des méthodes sur une structure (c'est-à-dire des fonctions pouvant être appelées "sur" la struct
, ou des valeurs de ce type de struct
), créez un bloc 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
indique ici une référence immuable à une instance de struct Foo
est nécessaire pour invoquer la méthode fiddle
. Si nous voulions modifier l'instance (comme changer l'un de ses champs), nous prendrions plutôt un &mut self
(c'est-à-dire une référence mutable):
impl Foo {
fn tweak(&mut self, n: isize) {
self.my_num = n;
}
}
// ...
foo.tweak(43);
assert_eq!(foo.my_num, 43);
Enfin, nous pourrions également utiliser self
(notez l'absence d'un &
) comme récepteur. Cela nécessite que l'instance appartienne à l'appelant et que l'instance soit déplacée lors de l'appel de la méthode. Cela peut être utile si vous souhaitez consommer, détruire ou transformer complètement une instance existante. Un exemple d'un tel cas d'utilisation est de fournir des méthodes de "chaînage":
impl Foo {
fn double(mut self) -> Self {
self.my_num *= 2;
self
}
}
// ...
foo.my_num = 1;
assert_eq!(foo.double().double().my_num, 4);
Notez que nous avons également préfixés self
avec mut
afin que nous puissions muter soi avant de le retourner à nouveau. Le type de retour de la méthode double
mérite également une explication. Self
à Self
intérieur d'un bloc d' impl
fait référence au type auquel l' impl
s'applique (dans ce cas, Foo
). Ici, c'est surtout un raccourci utile pour éviter de retaper la signature du type, mais dans les traits, il peut être utilisé pour faire référence au type sous-jacent qui implémente un trait particulier.
Déclarer une méthode associée (communément appelée "méthode de classe" dans d’autres langages) pour une struct
tout simplement self
argument de self
. De telles méthodes sont appelées sur le type de struct
lui-même, et non sur une instance de celui-ci:
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);
Notez que les méthodes de structure ne peuvent être définies que pour les types déclarés dans le module actuel. De plus, comme pour les champs, toutes les méthodes de structure sont privées par défaut et ne peuvent donc être appelées que par code dans le même module. Vous pouvez préfixer les définitions avec le mot-clé pub
pour les rendre appelables ailleurs.
Structures génériques
Les structures peuvent être génériques sur un ou plusieurs paramètres de type. Ces types sont indiqués entre <>
en référence au 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};
Plusieurs types peuvent être donnés en utilisant des virgules:
struct Gen2<T, U> {
x: T,
y: U,
}
// ...
let _: Gen2<bool, isize> = Gen2{x: true, y: 42};
Les paramètres de type font partie du type, donc deux variables du même type de base, mais avec des paramètres différents, ne sont pas interchangeables:
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
Si vous voulez écrire une fonction qui accepte une struct
indépendamment de son affectation de paramètre de type, cette fonction devra également être générique:
fn hello<T>(g: Gen<T>) {
println!("{}", g.z); // valid, since g.z is always an isize
}
Mais si nous voulions écrire une fonction qui pourrait toujours imprimer gx
? Il faudrait restreindre T
à un type pouvant être affiché. Nous pouvons le faire avec des limites de type:
use std::fmt;
fn hello<T: fmt::Display>(g: Gen<T>) {
println!("{} {}", g.x, g.z);
}
La fonction hello
est désormais définie uniquement pour les instances Gen
dont le type T
implémente fmt::Display
. Si on essayait de passer un Gen<(bool, isize)>
par exemple, le compilateur se plaindrait que hello
n'est pas défini pour ce type.
Nous pouvons également utiliser des limites de type directement sur les paramètres de type de la struct
pour indiquer que vous ne pouvez construire cette struct
que pour certains types:
use std::hash::Hash;
struct GenB<T: Hash> {
x: T,
}
Toute fonction ayant accès à un GenB
sait maintenant que le type de x
implémente Hash
, et donc qu’elle peut appeler .x.hash()
. Des limites de type multiples pour le même paramètre peuvent être données en les séparant par un +
.
Comme pour les fonctions, les limites de type peuvent être placées après le <>
utilisant le mot-clé where
:
struct GenB<T> where T: Hash {
x: T,
}
Cela a la même signification sémantique, mais peut rendre la signature plus facile à lire et à formater lorsque vous avez des limites complexes.
Les paramètres de type sont également disponibles pour les méthodes d'instance et les méthodes associées de la 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}
}
}
Si vous avez des limites de type sur le T
Gen
, celles-ci devraient également être reflétées dans les limites de type de l' impl
. Vous pouvez également rendre les limites d' impl
plus strictes pour indiquer qu'une méthode donnée n'existe que si le type satisfait une propriété particulière:
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