Google annonce la sortie de Dart 3 et apporte une révision majeure du langage, les améliorations concernent la sécurité null, les modificateurs de classe, les motifs et les enregistrements

Bonjour depuis Google I/O 2023. Aujourd'hui, en direct de Mountain View, Google annonce Dart 3, la plus grande version de Dart à ce jour ! Dart 3 comporte trois avancées majeures. Tout d'abord, la sécurité null sonore est désormais assurée à 100 %. Deuxièmement, de nouvelles fonctionnalités linguistiques majeures pour les enregistrements, les motifs et les modificateurs de classe ont été ajoutés. Enfin, un avant-goût de l'avenir est donné par un élargissement de la prise en charge des plates-formes avec du code natif pour le web via la compilation Wasm. Entrons dans les détails.


Une sécurité null complète

Au cours des quatre dernières années, nous avons fait évoluer Dart pour en faire un langage rapide, portable et moderne. Aujourd'hui, avec Dart 3, c'est un langage 100 % sécurisé pour le null ! Comme nous l'avons déjà dit, nous ne pensons pas qu'un autre langage de programmation ait jamais ajouté la sécurité null à un langage existant. Ce fut donc un véritable parcours du combattant.

Nom : dart 3.png
Affichages : 21376
Taille : 20,7 Ko

Avec 100 % de sécurité null dans Dart, on a un système solide. Vous pouvez être sûr que si un type dit qu'une valeur n'est pas null, alors elle ne peut jamais être null. Cela permet d'éviter certaines catégories d'erreurs de codage, comme les exceptions de pointeur nul. Cela permet également aux compilateurs et aux systèmes d'exécution d'optimiser le code d'une manière qui ne serait pas possible sans la sécurité des valeurs nulles. Ce choix de conception implique un compromis. Bien que les migrations soient devenues un peu plus difficiles, le choix fait pour Dart est le bon.

Migration vers Dart 3

Le soutien indéfectible de la communauté Dart a joué un rôle essentiel dans l'obtention d'une sécurité nulle : 99 % des 1000 premiers paquets sur pub.dev supportent la sécurité nulle !

Compte tenu de cela, on s'attend à ce que la grande majorité des paquets et des applications qui ont été migrés vers la sécurité null fonctionnent avec Dart 3. Dans quelques cas seulement, une petite quantité de nettoyage dans Dart 3 pourrait avoir un impact sur certains codes. Certaines anciennes API de la bibliothèque principale ont été supprimées et certains outils ont été ajustés. Si vous rencontrez des problèmes lors de la migration vers le SDK Dart 3, veuillez consulter le guide de migration Dart 3. Pour le reste, vous apprécierez les nouvelles bibliothèques de base et les nouveaux outils rationalisés.

Nom : dart 3 timeline.jpg
Affichages : 2863
Taille : 24,0 Ko

Principales fonctionnalités du langage - Enregistrements, patterns et modificateurs de classe

Dart 3 ne se contente pas de modifier le langage existant. Il s'agit également d'ajouter de nouvelles fonctionnalités et capacités significatives ! Il s'agit notamment des enregistrements, des motifs et des modificateurs de classe.

Construire des données structurées avec des enregistrements

Traditionnellement, une fonction Dart ne pouvait renvoyer qu'une seule valeur. Par conséquent, les fonctions qui devaient renvoyer plusieurs valeurs devaient soit les intégrer dans d'autres types de données tels que des cartes ou des listes, soit définir de nouvelles classes pouvant contenir les valeurs. L'utilisation de structures de données non typées affaiblit la sécurité des types. Le fait de devoir définir de nouvelles classes uniquement pour transporter des données ajoute de la friction au processus de codage. Vous avez été très clairs à ce sujet : la demande de langage pour des valeurs de retour multiples est le quatrième problème le plus évalué.

Avec les enregistrements, vous pouvez construire des données structurées avec une syntaxe claire et précise. Prenons l'exemple de cette fonction. Elle lit le nom et l'âge d'un blob JSON et les renvoie dans un enregistrement :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
(String, int) userInfo(Map<String, dynamic> json) {
  return (json['name'] as String, json['height'] as int);
}

Cela devrait sembler familier à tous les développeurs Dart. Un enregistrement ressemble à une liste littérale telle que ['Michael', 'Product Manager'] mais utilise des parenthèses au lieu de crochets. Dans Dart, les enregistrements sont une caractéristique générale. Ils peuvent être utilisés pour d'autres choses que les valeurs de retour des fonctions. Vous pouvez également les stocker dans des variables, les placer dans une liste, les utiliser comme clés dans une carte ou créer des enregistrements contenant d'autres enregistrements. Vous pouvez ajouter des champs sans nom, comme dans l'exemple précédent, et des champs nommés comme (42, description : "Sens de la vie").

Les enregistrements sont des types de valeurs et n'ont pas d'identité. Cela permet à nos compilateurs d'effacer complètement l'objet enregistrement dans certains cas. Les enregistrements sont également dotés d'un opérateur == et de fonctions hashCode définis automatiquement. La documentation sur les enregistrements contient plus de détails.

Travailler avec des données structurées à l'aide de motifs et du filtrage par motifs

Les enregistrements simplifient la construction de données structurées. Cela ne remplace pas l'utilisation de classes pour construire des hiérarchies de types plus formelles. Il s'agit simplement d'une autre option. Dans les deux cas, il est possible que vous souhaitiez décomposer ces données structurées en éléments individuels afin de pouvoir les utiliser. C'est là que le filtrage par motifs entre en jeu.

Considérons une forme élémentaire de modèle. Le motif d'enregistrement suivant déstructure un enregistrement en deux nouvelles variables : name et height. Ces variables peuvent ensuite être utilisées comme n'importe quelle autre variable, par exemple dans un appel à print :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
var (String name, int height) = userInfo({'name': 'Michael', 'height': 180});
print('User $name is $height cm tall.');

Des motifs similaires existent pour les listes et les cartes. Pour tous ces motifs, vous pouvez sauter des éléments individuels avec le motif de soulignement :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
var (String name, _) = userInfo();

Les motifs brillent lorsqu'ils sont utilisés dans une instruction switch. Depuis le début, Dart n'offre qu'un support limité pour les commutations. Dans Dart 3, la puissance et l'expressivité de l'instruction switch ont été élargies. Le filtrage par motifs est désormais prise en charge dans ces cas. La nécessité d'ajouter un break à la fin de chaque cas a été supprimée. Les opérateurs logiques sont également pris en charge pour combiner les cas. L'exemple suivant montre une instruction switch claire et précise qui analyse un code de caractère :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
switch (charCode) {
  case slash when nextCharCode == slash:
    skipComment();
 
  case slash || star || plus || minus:
    operator(charCode);
 
  case >= digit0 && <= digit9:
    number();
 
  default:
    invalid();
}

L'instruction switch est très utile lorsque vous avez besoin d'une ou plusieurs instructions pour chaque case. Dans certains cas, il suffit de calculer une valeur. Pour ce cas d'utilisation, une expression switch très succincte est proposée. Cette expression ressemble à l'instruction switch, mais utilise une syntaxe différente, adaptée aux expressions. L'exemple de fonction suivant renvoie la valeur d'une expression switch pour calculer une description du jour de la semaine d'aujourd'hui :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
String describeDate(DateTime dt) =>
  switch (dt.weekday) {
      1 => 'Feeling the Monday blues?',
      6 || 7 => 'Enjoy the weekend!',
      _ => 'Hang in there.'
  };

Une caractéristique puissante des motifs est la possibilité de vérifier l'"exhaustivité". Cette caractéristique garantit que le commutateur traite tous les cas possibles. Dans l'exemple précédent, toutes les valeurs possibles de weekday, qui est un int, sont prises en compte. Les valeurs possibles sont épuisées par la combinaison d'instructions de correspondance pour les valeurs spécifiques 1, 6 ou 7, puis par l'utilisation d'un cas par défaut _ pour les cas restants. Pour activer cette vérification pour les hiérarchies de données définies par l'utilisateur, telles qu'une hiérarchie de classes, utilisez le nouveau modificateur sealed au sommet de la hiérarchie de classes, comme dans l'exemple suivant :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
sealed class Animal {}
class Cow extends Animal {}
class Sheep extends Animal {}
class Pig extends Animal {}
 
String whatDoesItSay(Animal a) =>
    switch (a) { Cow c => '$c says moo', Sheep s => '$s says baa' };

L'erreur suivante est obtenue, signalant que le dernier sous-type possible, Pig, n'a pas été pris en compte :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
line 6 • The type 'Animal' is not exhaustively matched by the switch cases
since it doesn't match 'Pig()'.

Enfin, les instructions if peuvent également utiliser des motifs. Dans l'exemple suivant, la correspondance if-case avec un motif map est utilisée pour déstructurer la carte JSON. À l'intérieur de celle-ci, la correspondance se fait avec des valeurs constantes (des chaînes comme 'name' et 'Michael') et un motif de test de type int h pour lire une valeur JSON. Si les correspondances échouent, Dart exécute l'instruction else.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
final json = {'name': 'Michael', 'height': 180};
 
// Find Michael's height.
if (json case {'name': 'Michael', 'height': int h}) {
  print('Michael is $h cm tall.'); 
} else { 
  print('Error: json contains no height info for Michael!');
}
Ceci n'est qu'un aperçu de tout ce qu'il est possible de faire avec les motifs. Ils deviendront omniprésents dans tout le code Dart. Pour en savoir plus, consultez la documentation sur les patterns et le codelab sur les patterns.

Contrôles d'accès fins pour les classes avec les modificateurs de classe

Les modificateurs de classe constituent une troisième caractéristique du langage Dart 3. Contrairement aux enregistrements et aux motifs que l'on attend de tous les développeurs Dart qu'ils utilisent, il s'agit plutôt d'une fonctionnalité destinée aux utilisateurs chevronnés. Elle répond aux besoins des développeurs Dart qui conçoivent de grandes surfaces d'API ou qui construisent des applications d'entreprise.

Les modificateurs de classe permettent aux auteurs d'API de ne prendre en charge qu'un ensemble spécifique de capacités. Les valeurs par défaut restent toutefois inchangées. Dart doit rester simple et accessible. Ainsi, comme auparavant, les classes ordinaires peuvent être construites, étendues et mises en œuvre, comme le montrent les exemples suivants :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Vehicle {
  String make; String model;
  void moveForward(int meters) {}
}
 
// Construct.
var myCar = Vehicle(make: 'Ford', model: 'T',);
 
// Extend.
class Car extends Vehicle {
  int passengers;
}
 
// Implement.
class MockVehicle implements Vehicle {
  @override void moveForward …
}

Les modificateurs de classe permettent d'ajouter des restrictions. Voici quelques exemples de cas d'utilisation :

  • Avec une interface class, vous pouvez définir un contrat à mettre en œuvre par d'autres. Une classe d'interface ne peut pas être étendue.
  • Avec une base class, vous pouvez vous assurer que tous les sous-types de votre classe en héritent, au lieu d'implémenter son interface. Cela permet de s'assurer que les méthodes privées sont disponibles pour toutes les instances.
  • Avec une final class, vous pouvez fermer la hiérarchie des types en empêchant toute sous-classe en dehors de votre propre bibliothèque. À titre d'exemple, cela permet au propriétaire de l'API d'ajouter de nouveaux membres sans risquer d'interrompre les modifications apportées aux consommateurs de l'API.

Pour plus de détails, voir la documentation sur les nouveaux modificateurs de classe.

Un aperçu de l'avenir

Dart 3 n'est pas seulement une avancée significative en termes de nouvelles fonctionnalités que vous pouvez utiliser dès aujourd'hui. Il vous donne également un avant-goût de ce qui vous attend.

Le langage Dart

Les enregistrements, les motifs et les modificateurs de classe sont de nouvelles fonctionnalités très importantes, il est donc possible que certaines parties de leur conception puissent être améliorées. Les commentaires des utilisateurs seront pris en compte et des mises à jour seront éventuellement nécessaires dans les versions mineures qui suivront Dart 3.

Des fonctionnalités plus petites et plus incrémentales sont également à l'étude. Elles ne constituent pas une rupture et visent à accroître la productivité des développeurs sans coût de migration. Deux exemples explorés sont les classes en ligne pour envelopper les types existants avec des "wrappers" à coût nul, et les constructeurs primaires ; une fonctionnalité qui introduit une syntaxe beaucoup plus concise pour définir des classes avec quelques champs et un constructeur primaire.

La question des macros (également appelées métaprogrammation) a été abordée précédemment. Google s'est particulièrement concentré sur ce point pour permettre une meilleure désérialisation de JSON (et similaires), et pour permettre les classes de données. Compte tenu de la taille et du risque inhérent à la métaprogrammation, une approche très approfondie est adoptée, et il n'y a donc pas de calendrier concret à partager, même pour la finalisation des décisions de conception.

Interopérabilité native

Les applications mobiles et de bureau s'appuient généralement sur de nombreuses API fournies par la plateforme native, qu'il s'agisse de notifications, de paiements ou de l'obtention de la localisation du téléphone. Traditionnellement, dans Flutter, on accède à ces API en construisant des plugins, ce qui nécessite d'écrire à la fois du code Dart pour l'API et un tas de code spécifique à la plateforme pour fournir l'implémentation.

L'interopérabilité avec le code qui se compile en bibliothèques C est déjà prise en charge par dart:ffi. L'interopérabilité avec Java et Kotlin sur Android, ainsi qu'avec Objective C et Swift sur iOS/macOS, est en cours d'extension. Pour une introduction à l'interopérabilité Android, consultez la nouvelle vidéo Google I/O 23 sur l'interopérabilité Android.

Compilation vers WebAssembly - cibler le web avec du code natif

WebAssembly (abrégé Wasm) a gagné en maturité en tant que format d'instruction binaire neutre pour tous les navigateurs modernes. Le framework Flutter utilise Wasm depuis un certain temps. C'est ainsi que le moteur de rendu graphique SKIA, écrit en C++, est livré au navigateur par le biais d'un module compilé en Wasm. Cela fait longtemps que le projet d'utiliser Wasm pour déployer du code Dart intéresse l'équipe, mais il n'y a pas eu d'avancée. Dart, comme beaucoup d'autres langages orientés objet, utilise le garbage collection. Au cours de l'année écoulée, Google a collaboré avec plusieurs équipes de l'écosystème Wasm pour ajouter une nouvelle fonctionnalité WasmGC à la norme WebAssembly. Cette fonctionnalité est maintenant presque stable dans les navigateurs Chromium et Firefox.

Le travail sur la compilation de Dart en modules Wasm a deux objectifs de haut niveau pour les applications web :

  • Temps de chargement : il est prévu de fournir des charges utiles de déploiement avec Wasm que le navigateur pourra charger plus rapidement, améliorant ainsi le temps nécessaire pour que l'utilisateur puisse interagir avec l'application web.
  • Performance : Les applications web basées sur JavaScript nécessitent une compilation juste à temps pour obtenir de bonnes performances. Les modules Wasm sont de plus bas niveau et plus proches du code machine, de sorte qu'ils peuvent offrir de meilleures performances avec moins de jank et des taux d'images plus constants.
  • Cohérence sémantique : Dart est fier d'être très cohérent entre les plateformes qu'il supporte. Cependant, sur le web, il y a quelques exceptions à cette règle. Par exemple, Dart web diffère actuellement dans la façon dont les nombres sont représentés. Avec les modules Wasm, il sera possible de traiter le web comme une plateforme "native" avec une sémantique similaire à celle des autres cibles natives.

Google est ravis d'annoncer aujourd'hui le premier aperçu de la compilation de Dart vers Wasm ! Dans un premier temps, le support web de Flutter est au centre des préoccupations. Il est encore tôt, et il reste beaucoup de travail à accomplir, mais vous êtes invités à expérimenter et à voir si cela vous enthousiasme.

Conclusion

Merci d'avoir lu jusqu'à la fin. Google espère que cet article vous a donné envie de découvrir Dart 3, disponible aujourd'hui à la fois dans le SDK Dart autonome et dans le SDK Flutter 3.10.

Une révision majeure du langage Dart a été réalisée, avec une sécurité null solide, et un nettoyage de la bibliothèque et des outils de base. De nouvelles fonctionnalités majeures du langage rendent Dart plus expressif et plus précis avec les enregistrements et les modèles. Pour les grandes surfaces d'API, les modificateurs de classe permettent un contrôle détaillé. Un aperçu de l'avenir est également inclus avec la prise en charge prochaine de WebAssembly.

Avec toutes ces fonctionnalités, Google pensez que Dart 3 illustre la vision à long terme de la société : Construire le langage de programmation le plus productif pour créer des applications rapides sur n'importe quelle plateforme.

Source : Google I/O 2023 (https://youtu.be/dBwvc-U8q-c)

Et vous ?

Qu'en pensez-vous ? Quelles fonctionnalités trouvez-vous intéressantes ?

Voir aussi

Dart 2.0 : le langage de programmation de Google sort officiellement en version stable, avec des améliorations de productivité et de performances

Dart 2.0 est disponible, le langage de Google a été optimisé pour le développement Web et mobile côté client

L'équipe de Dart annonce l'arrivée d'une « sécurité null » dans le langage de programmation pour permettre aux développeurs de rendre leurs applications plus stables et plus performantes