Anunciando Dart 2.13

Nueva característica del lenguaje de type aliases, Dart FFI mejorado

Banner del release de Dart 2.13 destacando type aliases, FFI más rápido, null safety, y soporte de Docker.

Por Kevin Moore y Michael Thomsen

Hoy anunciamos Dart 2.13, que incluye type aliases — actualmente nuestra segunda característica de lenguaje más solicitada. Dart 2.13 también incluye Dart FFI mejorado y mejor rendimiento, y tenemos nuevas Docker Official Images para Dart. Este post da una actualización sobre la característica de null safety introducida en 2.12, discute las nuevas características de 2.13, tiene algunas noticias emocionantes sobre el soporte de Docker y Google Cloud para backends de Dart, y presenta algunos cambios que puedes esperar ver en futuros releases.

Actualización de null safety

#

Lanzamos sound null safety en marzo en el release de Dart 2.12. Null safety es la última gran característica de productividad de Dart, diseñada para ayudarte a evitar errores de null — una clase de bugs que a menudo son difíciles de detectar. Con ese lanzamiento animamos a los publicadores de paquetes a comenzar a migrar los paquetes compartidos en pub.dev a null safety.

¡Hemos estado extremadamente complacidos de ver lo rápido que se ha adoptado null safety! Apenas unos meses después del lanzamiento, el 93% de los top-500 paquetes más populares en pub.dev ya soportan null safety. Nos gustaría extender nuestro sincero agradecimiento a todos los desarrolladores de paquetes por hacer este trabajo tan rápido, y por ayudar a todo el ecosistema a avanzar!

Con tantos paquetes soportando null safety, hay una buena posibilidad de que puedas comenzar a migrar tus apps para usar null safety. El primer paso es usar dart pub outdated para verificar las dependencias de tu app. Para más detalles, consulta la guía de migración a null safety. También hemos cambiado nuestras plantillas de dart create y flutter create para que ahora habiliten null safety por defecto en nuevas apps y paquetes.

Anunciando type aliases

#

Type aliases es una nueva característica en el lenguaje 2.13. Extiende nuestro soporte anterior, que permitía crear alias de tipos para tipos de funciones, pero no para otros tipos. Esta característica muy buscada fue la segunda más valorada en el issue tracker del lenguaje.

Usando un type alias puedes crear un nuevo nombre para cualquier tipo existente, que luego puede usarse en cualquier lugar donde el tipo original podría usarse. No estás realmente definiendo un nuevo tipo, solo introduciendo un alias abreviado. El alias incluso pasa las pruebas de igualdad de tipos:

typedef Integer = int;

void main() {
  print(int == Integer); // true
}

¿Entonces para qué puedes usar type aliases? Un uso común es darle un nombre más corto o más descriptivo a un tipo, haciendo tu código más legible y mantenible.

Un buen ejemplo es trabajar con JSON (gracias al usuario de GitHub Levi-Lesches por este ejemplo). Aquí podemos definir un nuevo type alias Json, que describe un documento JSON como un map de claves String a cualquier valor (usando el tipo dynamic). Luego podemos usar ese type alias de Json al definir nuestro constructor nombrado fromJson y nuestro getter json.

typedef Json = Map<String, dynamic>;

class User {
  final String name;
  final int age;

  User.fromJson(Json json) :
    name = json['name'],
    age = json['age'];

Json get json => {
    'name': name,
    'age': age,
  };
}

También puedes llamar constructores en un type alias que nombra una clase, así que lo siguiente es perfectamente legal:

main() {
  var j = Json();
  j['name'] = 'Michael';
}

Al usar type aliases para dar nombres a tipos complejos, puedes facilitar a los lectores entender los invariantes de tu código. Por ejemplo, el siguiente código define un type alias para describir maps que contienen claves de tipo genérico X y valores de tipo List<X>. Al darle al tipo un nombre con un solo parámetro de tipo, la estructura regular del map se vuelve más aparente para el lector del código.

typedef MapToList<X> = Map<X, List<X>>;
void main() {
  MapToList<int> m = {};
  m[7] = [7]; // OK
  m[8] = [2, 2, 2]; // OK
  for (var x in m.keys) {
    print('$x --> ${m[x]}');
  }
}

=>

7 --> [7]
8 --> [2, 2, 2]

Si intentas usar tipos que no coinciden, obtendrás un error de análisis:

m[42] = ['The', 'meaning', 'of', 'life'];

=>

The element type 'String' can't be assigned to the list type 'int'.

Incluso puedes usar type aliases al renombrar clases en librerías públicas. Imagina que tienes una clase existente PoorlyNamedClass en una librería pública que quieres renombrar a BetterNamedClass. Si simplemente renombras la clase, tus clientes de la API obtendrán errores de compilación repentinos. Con type aliases puedes hacer el renombrado, pero luego definir un nuevo type alias para el nombre antiguo de la clase, y luego añadir una anotación @Deprecated para el nombre antiguo. Los usos de PoorlyNamedClass entonces causarán una advertencia al ser usados, pero seguirán compilando y funcionando como antes, dando a los usuarios tiempo para actualizar su código.

Así es como podrías implementar BetterNamedClass y deprecar PoorlyNamedClass (en un archivo llamado mylibrary.dart):

class BetterNamedClass {...}

@Deprecated('Use BetterNamedClass instead')
typedef PoorlyNamedClass = BetterNamedClass;

Y esto es lo que pasa cuando alguien intenta usar PoorlyNamedClass:

import 'mylibrary.dart';

void main() {
  PoorlyNamedClass p;
}

=>

'PoorlyNamedClass' is deprecated and shouldn't be used. Use BetterNamedClass instead.

La característica de type alias está disponible a partir de Dart 2.13. Para habilitarla, establece la restricción inferior del SDK de Dart en tu pubspec a al menos 2.13:

environment:
  sdk: ">=2.13.0 <3.0.0"

Esta característica es compatible hacia atrás, gracias al versionado del lenguaje. Los paquetes con restricciones de SDK inferiores a 2.13 pueden referirse de forma segura a type aliases definidos en paquetes 2.13, a pesar de que los paquetes pre-2.13 no pueden definir sus propios type aliases.

Cambios de FFI en Dart 2.13

#

También tenemos un par de características nuevas en Dart FFI, nuestro mecanismo de interop para llamar código C.

Primero, FFI ahora soporta structs que tienen arrays inline (#35763). Considera una struct de C con un array inline como este:

struct MyStruct {
  uint8_t arr[8];
}

Ahora puedes envolver eso en Dart directamente, especificando el tipo de elemento con un argumento de tipo para Array:

class StructInlineArray extends Struct {
  [@Array](http://twitter.com/Array)(8)
  external Array<Uint8> arr;
}

Segundo, FFI ahora soporta structs packed (#38158). Normalmente las structs se disponen en memoria de modo que los miembros caen en límites de dirección que son más fáciles de acceder para la CPU. Con structs packed parte de este padding se omite para reducir el consumo general de memoria, a menudo de formas específicas de la plataforma. Con la nueva anotación @Packed(<alignment>), puedes especificar fácilmente el padding. Por ejemplo, el siguiente código crea una struct que tiene alineación de 4 bytes cuando está en memoria:

[@Packed](http://twitter.com/Packed)(4)
class TASKDIALOGCONFIG extends Struct {
  [@Uint32](http://twitter.com/Uint32)()
  external int cbSize;
  [@IntPtr](http://twitter.com/IntPtr)()
  external int hwndParent;
  [@IntPtr](http://twitter.com/IntPtr)()
  external int hInstance;
  [@Uint32](http://twitter.com/Uint32)()
  external int dwFlags;
  ...
}

Cambios de rendimiento en Dart 2.13

#

Continuamos trabajando en reducir el tamaño de la aplicación y la huella de memoria del código Dart. En apps grandes de Flutter, las estructuras internas que representan metadatos de un programa Dart compilado con AOT pueden ocupar una porción considerable de memoria. La mayor parte de estos metadatos están presentes para habilitar características como hot reload, debugging interactivo, y formateo de stack traces legibles para humanos — características que nunca se usan en aplicaciones desplegadas. Durante el último año hemos estado reestructurando el runtime nativo de Dart para eliminar tanta sobrecarga como sea posible. Algunas de estas mejoras aplican a todas las apps de Flutter construidas en modo release, pero algunas requieren que prescindas de stack traces legibles separando la información de debug de las aplicaciones compiladas con AOT usando el flag --split-debug-info.

Dart 2.13 incluye varios cambios que reducen significativamente el espacio ocupado por los metadatos del programa cuando se usa --split-debug-info. Toma como ejemplo la app de Flutter Gallery. En Android el release APK es de 112.4 MB con información de debug, y 106.7 MB sin ella (una reducción general del 5%). Este APK contiene muchos assets. Mirando solo los metadatos del código dentro del APK, se redujo de 5.7MB en Dart 2.12 a solo 3.7MB en Dart 2.13 (una reducción del 35%).

Si el tamaño de la app y la huella de memoria son importantes para ti, considera omitir la información de debug usando el flag --split-debug-info. Ten en cuenta que al hacerlo necesitarás usar el comando symbolize para hacer que los stack traces sean legibles para humanos nuevamente.

Soporte oficial de Docker y Dart en Google Cloud

#

Dart está ahora disponible como Docker Official Images. Aunque Dart ha proporcionado imágenes de Docker durante años, estas nuevas imágenes de Dart son testeadas y validadas por Docker para seguir las mejores prácticas. También soportan compilación ahead-of-time (AOT), lo que puede reducir dramáticamente el tamaño de los contenedores construidos y mejorar la velocidad de despliegue en entornos de contenedores — como Cloud Run.

Mientras que Dart sigue enfocado en habilitar frameworks de apps como Flutter para impulsar hermosos píxeles en cada pantalla, nos damos cuenta de que detrás de la mayoría de las experiencias de usuario hay al menos un servicio alojado. Al facilitar la construcción de servicios backend con Dart, soportamos una experiencia full stack que permite a los desarrolladores extender su aplicación a la nube usando el mismo lenguaje y lógica de negocio que usan para potenciar widgets en el frontend.

En general, usar Dart para backends de apps de Flutter es especialmente adecuado para la simplicidad y escalabilidad de la plataforma serverless gestionada de Google, Cloud Run. Esto incluye scale-to-zero, lo que significa que no incurres costos cuando tu backend no está manejando ninguna petición. Trabajamos con el equipo de Google Cloud para proporcionar el Functions Framework for Dart, una colección de paquetes, herramientas y ejemplos que facilitan escribir funciones Dart para desplegar en lugar de servidores completos para manejar peticiones HTTP y CloudEvents.

Revisa nuestra documentación de Google Cloud para comenzar.

Algunas palabras sobre lo que viene

#

Ya estamos trabajando en algunos cambios emocionantes para los próximos releases. Como siempre, puedes usar el tracker de language funnel para mantener un ojo en nuestro progreso.

Un área en la que estamos trabajando es un nuevo conjunto de lints canónicos tanto para Dart como para Flutter. Los lints son una forma poderosa de configurar el análisis estático de Dart, pero con cientos de lints posibles para activar o desactivar, puede ser difícil decidir qué elegir. Actualmente estamos trabajando en definir dos conjuntos canónicos de lints que aplicaremos por defecto en proyectos de Dart y Flutter. Esperamos que esto esté habilitado por defecto en el próximo release estable. Si quieres un preview, revisa los dos paquetes lints y flutter_lints.

Finalmente, si haces embedding profundo del runtime del Dart VM, ten en cuenta que estamos planeando deprecar el mecanismo existente para esto. Lo reemplazaremos con un modelo más rápido y flexible basado en Dart FFI (ver issue de seguimiento #45451).

Dart 2.13 está disponible ahora

#

Dart 2.13, con type aliases y FFI mejorado, está disponible hoy en los SDKs de Dart 2.13 y Flutter 2.2.

Si has estado esperando a que tus dependencias migren a null safety, quizás quieras revisar nuevamente, usando dart pub outdated. Con el 93% de los top-500 paquetes más populares ya migrados, hay una buena posibilidad de que estés desbloqueado. ¡También nos gustaría extender un gran agradecimiento a los desarrolladores que ya han migrado!

Nos encantaría escuchar sobre tu experiencia con las nuevas características y cambios discutidos en este blog post. Deja un comentario abajo o twittea a @dart_lang.

Más de Dart