Structure Rust

En Rust, les structures et les tuples peuvent tous deux regrouper plusieurs données de types éventuellement différents en un tout, mais chaque membre et la structure elle-même ont un nom, donc vous n'avez pas besoin de vous souvenir des indices lors de l'accès à ses membres. Les tuples sont souvent utilisés pour le passage ponctuel de plusieurs valeurs, tandis que les structures sont utilisées pour définir des structures de données couramment utilisées. Chaque membre d'une structure est appelé un « champ ».

Définition de structure

Voici une définition de structure :

struct Site {
    domain: String,
    name: String,
    nation: String,
    found: u32
}

Remarque : si vous êtes habitué au C/C++, rappelez-vous qu'en Rust l'instruction struct ne sert qu'à la définition, elle ne peut pas déclarer d'instances, et elle n'a pas besoin d'un;symbole, et après chaque définition de champ, utilisez,pour séparer.

Instance de structure

Rust est influencé par JavaScript à de nombreux endroits. Lors de l'instanciation d'une structure, il utilise la syntaxekey: valuede l'objet JSON pour réaliser la définition :

Exemple

let example = Site {
    domain: String::from("www.example.com"),
    name: String::from("EXAMPLE"),
    nation: String::from("China"),
    found: 2013
};

Si vous ne connaissez pas les objets JSON, vous pouvez les ignorer et simplement retenir le format :

Struct class name {
    field name : field value,
    ...
}

L'avantage de cela est que non seulement cela rend le programme plus intuitif, mais cela ne nécessite pas non plus de saisir les valeurs des membres dans l'ordre où elles ont été définies.

Si la structure instanciée possède des noms de champs qui correspondent à des noms de variables existants, vous pouvez simplifier l'écriture :

Exemple


let domain = String::from("www.example.com");
let name = String::from("EXAMPLE");
let example = Site {
    domain,  // équivalent à domain: domain,
    name,    // équivalent à name: name,
    nation: String::from("China"),
    trafic: 2013
};

Il existe une situation : vous souhaitez créer une nouvelle instance de structure où la plupart des propriétés doivent être définies avec les mêmes valeurs qu'une structure existante, et vous ne devez modifier qu'une ou deux valeurs de champ. Vous pouvez utiliser la syntaxe de mise à jour de structure :

let site = Site {
    domain: String::from("www.example.com"),
    name: String::from("EXAMPLE"),
    ..example
};

Remarque :..exampleIl ne peut pas y avoir de virgule après. Cette syntaxe ne permet pas de copier exactement une autre instance de structure sans modifications ; c'est-à-dire que vous devez définir la valeur d'au moins un champ avant de référencer les valeurs d'une autre instance.

Structure tuple

Il existe une manière plus simple de définir et d'utiliser des structures :Structure tuple。

Une structure tuple est une structure qui a la forme d'un tuple.

La différence avec un tuple est qu'elle possède un nom et un format de type fixe. Elle existe pour gérer des données simples qui nécessitent un type défini (pour un usage fréquent) sans être trop complexe :

struct Color(u8, u8, u8);
struct Point(f64, f64);

let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);

« Color » et « coordonnées de point » sont deux types de données couramment utilisés, mais si vous écrivez des accolades et deux noms lors de l'instanciation, vous sacrifiez la commodité au profit de la lisibilité. Rust ne laisse pas ce problème sans solution. Les objets de structure tuple s'utilisent de la même manière que les tuples, accessibles via . et les indices :

Exemple

fn main() {
    struct Color(u8, u8, u8);
    struct Point(f64, f64);

    let black = Color(0, 0, 0);
    let origin = Point(0.0, 0.0);

    println!("black = ({}, {}, {})", black.0, black.1, black.2);
    println!("origin = ({}, {})", origin.0, origin.1);
}

Sortie :

black = (0, 0, 0)
origin = (0, 0)

Propriété des structures

Une structure doit posséder les valeurs de ses champs, car lorsque la structure sort de la portée, elle libère tous ses champs.

C'est pourquoi les exemples de ce chapitre utilisent le type String au lieu de &str.

Mais cela ne signifie pas que des champs de référence ne peuvent pas être définis dans des structures ; cela nécessite le mécanisme de "durée de vie".

Mais le concept de "durée de vie" est difficile à expliquer maintenant, il ne pourra donc être expliqué que dans les chapitres suivants.

Affichage des structures

Lors du débogage, afficher une instance complète de structure est très utile. Mais écrire manuellement un format serait très peu pratique. Rust fournit donc un moyen pratique d'afficher une structure entière :

Exemple

#[derive(Debug)]

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    println!("rect1 is {:?}", rect1);
}

Comme indiqué dans la première ligne : assurez-vous d'importer la bibliothèque de débogage#[derive(Debug)], puis dans les macros println et print vous pouvez utiliser{:?}un espace réservé pour afficher une structure entière :

rect1 is Rectangle { width: 30, height: 50 }

S'il y a beaucoup d'attributs, vous pouvez utiliser un autre espace réservé{:#?} 。

Sortie :

rect1 is Rectangle {
    width: 30,
    height: 50
}

Méthodes de structure

Les méthodes sont similaires aux fonctions, sauf qu'elles sont utilisées pour opérer sur des instances de structure.

Si vous avez étudié les langages orientés objet, vous devez savoir que les fonctions sont généralement placées dans les définitions de classe et utilisent "this" dans la fonction pour faire référence à l'instance sur laquelle elles opèrent.

Rust n'est pas un langage orienté objet, comme le montre l'innovation de son mécanisme de propriété. Cependant, les idées précieuses de la programmation orientée objet peuvent être implémentées en Rust.

Le premier paramètre d'une méthode de structure doit être &self, et son type n'a pas besoin d'être déclaré, car self n'est pas un style mais un mot-clé.

Calculez l'aire d'un rectangle :

Exemple

struct Rectangle {
    width: u32,
    height: u32,
}
   
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("L'aire de rect1 est {}", rect1.area());
}

Sortie :

rect1's area is 1500

Notez que lorsque vous appelez une méthode de structure, vous n'avez pas besoin de renseigner self ; c'est pour des raisons de commodité.

Un exemple avec plusieurs paramètres :

Exemple

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn wider(&self, rect: &Rectangle) -> bool {
        self.width > rect.width
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 40, height: 20 };

    println!("{}", rect1.wider(&rect2));
}

Sortie :

false

Ce programme calcule si rect1 est plus large que rect2.


Fonctions associées de structure

La raison pour laquelle les « méthodes de structure » ne sont pas appelées « fonctions de structure » est que le nom « fonction » est réservé aux fonctions qui sont dans un bloc impl mais qui n'ont pas de paramètre &self.

De telles fonctions ne dépendent pas d'une instance, mais pour les utiliser, vous devez spécifier dans quel bloc impl elles se trouvent.

La fonction toujours utiliséeString::fromest une « fonction associée ».

Exemple

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn create(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let rect = Rectangle::create(30, 50);
    println!("{:?}", rect);
}

Sortie :

Rectangle { width: 30, height: 50 }

Astuce :Le bloc impl d'une struct peut être écrit plusieurs fois ; l'effet équivaut à concaténer leurs contenus !

Struct unitaire

Une struct peut servir de symbole sans aucun membre :

struct UnitStruct;

On appelle cette struct sans corps une struct unitaire.

Autres extensions