Dart Record (enregistrement / tuple)

I. Qu’est-ce qu’un Record ?

Le Record est un type composite, immuable et anonyme qui permet de regrouper plusieurs données de types différents en un seul objet. Voici ses différences clés par rapport aux List / Set / Map :

  • Taille fixe : une fois créé, le nombre de champs ne peut pas augmenter ni diminuer ; stockage hétérogène :
  • Un même Record peut contenir des valeurs de types totalement distincts comme int, String, bool
  • Contrainte typage statique : le compilateur reconnaît le type de chaque champ, la sécurité de type est préservée
  • Lecture seule et non modifiable : les valeurs des champs ne peuvent pas être réaffectées après création

Un Record est une variable valide à part entière, il supporte toutes les opérations classiques : stockage dans une variable, passage en paramètre / plusieurs retours de fonction, imbrication, insertion dans List/Map/Set.

II. Syntaxe de base du Record

1. Littéral Record

Encadré par des parenthèses, séparé par des virgules, il se divise en deux catégories : champs positionnels et champs nommés. Les champs positionnels s’écrivent en premier, les champs nommés suivent avec la syntaxe clé:.

// Mélange champs positionnels + champs nommés
var info = ('Jim', age: 20, flag: true, 'Student');Langage du code : JavaScript (javascript)

2. Utilisation du Record

1 Champs positionnels

// Déclaration : deux champs positionnels, respectivement String et int
(String, int) user;

// Affectation correspondant à la structure
user = ('李四', 22);
Langage du code : JavaScript (javascript)

2 Champs nommés

La déclaration de type utilise des accolades {}

// Déclarer deux champs nommés a(int), b(bool)
({int a, bool b}) data;

data = (a: 100, b: false);Langage du code : JavaScript (javascript)

3 Utilisation mixte

Déclarer un mélange de champs positionnels et nommés dans le type

// 1 champ positionnel String, 2 champs nommés score(int) et pass(bool)
(String, {int score, bool pass}) exam;
exam = ('Examen final', score: 90, pass: true);
Langage du code : JavaScript (javascript)

III. Erreurs courantes

1 : Le nom des champs nommés fait partie de la structure de type ; des noms différents créent deux types distincts
// Type : ({int a, int b})
({int a, int b}) pointAB = (a: 1, b: 2);
// Type : ({int x, int y})
({int x, int y}) pointXY = (x: 3, y: 4);

// Erreur de compilation ! Types incompatibles, affectation interdite
// pointAB = pointXY;
Langage du code : JavaScript (javascript)

Pour les champs nommés, même si ce sont deux entiers, les noms ab et xy diffèrent donc l’affectation est impossible. Voir ci-dessous pour les champs positionnels.

2 : Les alias sur champs positionnels dans l’annotation de type ne sont que des commentaires, sans impact sur la correspondance de type

Les noms donnés aux champs positionnels entre parenthèses ne sont que de la documentation pour le développeur, ignorés par le compilateur. Si la structure est identique, l’affectation est autorisée :

(int a, int b) p1 = (10, 20);
(int x, int y) p2 = (30, 40);

// Valide : sous-jacent deux champs int positionnels, types identiques
p1 = p2;Langage du code : JavaScript (javascript)

Analogie avec les paramètres de fonction : renommer les paramètres ne change pas la signature de la fonction, logique identique.

IV. Lire les champs internes d’un Record

Le Record est immuable, il ne propose que des getters sans setters, impossible de modifier les valeurs des champs.

  1. Champs nommés : accès direct via .nomDuChamp
  2. Champs positionnels : accès par $chiffre ; le chiffre ignore tous les champs nommés et ne compte que l’ordre des champs positionnels
var record = ('first', a: 2, b: true, 'last');

print(record.$1); // Premier champ positionnel : first
print(record.a);  // Champ nommé a : 2
print(record.b);  // Champ nommé b : true
print(record.$2); // Second champ positionnel : last
Langage du code : PHP (php)

V. Règles de détermination du type structurel Record

Le Record ne nécessite pas de définition de classe séparée ; le type est déterminé par sa forme structurelle :

Forme = nombre total de champs + type de chaque champ + noms des champs nommés

Dès que la forme est strictement identique, même provenant de fichiers ou bibliothèques différents, ils appartiennent au même type.

Le compilateur identifie en permanence le type statique de chaque champ, aucune perte d’informations de type :

// Position 1 : type num, position 2 : type Object
(num, Object) pair = (42, 'Un texte');

var firstVal = pair.$1; // Type statique num, runtime int
var secondVal = pair.$2;// Type statique Object, runtime StringLangage du code : JavaScript (javascript)

VI. Comparaison d’égalité Record ==

Deux conditions doivent être réunies pour être égaux :

  1. Les deux Records ont une forme strictement identique
  2. Toutes les valeurs des champs positionnels / nommés correspondants sont égales

L’ordre d’écriture des champs nommés n’influence pas la comparaison d’égalité ; en revanche des noms de champs différents impliquent des types distincts et donc une inégalité systématique.

// Champs positionnels, même forme et valeurs identiques → true
(int x, int y, int z) point = (1, 2, 3);
(int r, int g, int b) color = (1, 2, 3);
print(point == color); // true

// Noms des champs nommés différents, types distincts → false
({int x, int y, int z}) p = (x:1, y:2, z:3);
({int r, int g, int b}) c = (r:1, g:2, b:3);
print(p == c); // falseLangage du code : PHP (php)

Le Record implémente automatiquement l’opérateur == et la méthode hashCode, aucune implémentation manuelle n’est requise.

VII. Retours multiples de fonction

Les fonctions Dart ne peuvent renvoyer qu’une seule valeur. On utilise le Record pour regrouper plusieurs données de types variés, associé au déstructuration par motif pour les séparer rapidement en variables indépendantes ; c’est une alternative parfaite aux List/Map (sans perte de sécurité de type).

1 Déstructuration champs positionnels

// Retour : Nom(String), âge(int)
(String name, int age) getUserInfo(Map<String, dynamic> json) {
  return (json['name'] as String, json['age'] as int);
}

void main() {
  final userJson = {'name': 'Dash', 'age': 10, 'color': 'blue'};
  // Déstructuration directe, deux variables sur une ligne
  var (userName, userAge) = getUserInfo(userJson);
  print(userName); // Dash
  print(userAge);  // 10
}
Langage du code : JavaScript (javascript)

2 Déstructuration champs nommés

Syntaxe :nomDuChamp

({String name, int age}) getUserInfo(Map<String, dynamic> json) {
  return (name: json['name'] as String, age: json['age'] as int);
}

void main() {
  final userJson = {'name': 'Dash', 'age': 10};
  // Syntaxe de déstructuration nommée
  final (:name, :age) = getUserInfo(userJson);
  print(name);
}
Langage du code : JavaScript (javascript)

Inconvénients des alternatives

Dans d’autres langages comme C++, C#, Java sans Record, il faut réaliser les opérations suivantes :

  1. Créer une nouvelle classe : code verbeux, constructeur et champs à écrire manuellement ;
  2. Retour via List : perte de sécurité de type, tout devient dynamic ;
  3. Retour via Map : risque de faute de frappe sur les clés, pas de vérification statique des types.

VIII. Données structurées légères

Lorsque vous n’avez besoin que de stocker des données sans méthodes, utilisez directement Record sans créer de classe. Cela supprime le code redondant de définition de classe et convient parfaitement aux listes de données en masse.

Exemple

Liste de configuration des boutons de page (exemple Flutter à titre illustratif, compréhensible après apprentissage de Flutter)

import 'package:flutter/material.dart';

void main() {
  final buttonList = [
    (
      label: "Téléverser un fichier",
      icon: const Icon(Icons.upload_file),
      onPressed: () => print("Clic sur le bouton de téléversement"),
    ),
    (
      label: "Voir les détails",
      icon: const Icon(Icons.info),
      onPressed: () => print("Clic sur le bouton détails"),
    )
  ];
}
Langage du code : JavaScript (javascript)

IX. typedef : alias de type pour Record

Simplifier les longs types

Les déclarations de type Record sont souvent longues. Utilisez typedef pour définir un alias de type, faciliter la réutilisation et modifier uniformément la structure des champs ultérieurement.

Utilisation basique
// Définir un alias pour le Record de bouton, onPressed autorisé à être null
typedef ButtonConfig = ({
  String label,
  Icon icon,
  void Function()? onPressed
});

// Déclarer une liste directement avec l'alias
List<ButtonConfig> buttons = [
  (label: "Valider", icon: const Icon(Icons.check), onPressed: () {}),
  (label: "Annuler", icon: const Icon(Icons.close), onPressed: null),
];
Langage du code : PHP (php)
Évolution future

Si vous devez ajouter des méthodes ou encapsuler de la logique sur les données plus tard, vous n’avez pas à modifier le code de parcours métier : remplacez uniquement le type :

  1. Solution 1 : remplacer par une classe classique
class ButtonConfig {
  final String label;
  final Icon icon;
  final void Function()? onPressed;
  ButtonConfig({required this.label, required this.icon, this.onPressed});
  // Ajout d'une méthode personnalisée
  bool get hasClickEvent => onPressed != null;
}
Langage du code : PHP (php)
  1. Solution 2 : envelopper l’ancien Record via Extension Type
extension type ButtonConfig._(({String label, Icon icon, void Function()? onPressed}) _) {
  String get label => _.label;
  Icon get icon => _.icon;
  void Function()? get onPressed => _.onPressed;

  ButtonConfig({required String label, required Icon icon, void Function()? onPressed})
      : this._((label: label, icon: icon, onPressed: onPressed));

  bool get hasClickEvent => _.onPressed != null;
}
Langage du code : PHP (php)

Avec ces deux modes de migration, le code métier qui boucle pour afficher les boutons sur la page reste inchangé.

X. Synthèse

  1. Prérequis version : Dart 3.0 et supérieur ;
  2. Trois caractéristiques principales : anonyme, immuable, composite ; taille fixe, stockage hétérogène, typage statique strict ;
  3. Deux familles de champs : champs positionnels $1/$2, champs nommés .nom ;
  4. Règle de typage : le type dépend de la forme structurelle, les noms des champs nommés participent à la forme ; les alias des champs positionnels ne sont que des commentaires ;
  5. Comparaison égalité : forme identique + toutes valeurs de champs égales → true ;
  6. Avantage majeur : retours multiples de fonction, conteneur de données léger, sécurité de type complète ;
  7. Extension possible : typedef pour simplifier les alias de type, migration transparente vers class / extension type à l’avenir.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *