Anunciando sound null safety

Haz tus aplicaciones más estables y performantes con el null safety de Dart

By: Filip Hracek & Michael Thomsen

Hoy es un hito importante para el equipo de Dart con la preview técnica de nuestro trabajo de null safety. Null safety te ayuda a evitar una clase de bugs que suelen ser difíciles de detectar, y como bonus adicional habilita una serie de mejoras de rendimiento. Ahora estamos lanzando una preview técnica temprana, y esperamos tus comentarios.

Esta publicación describe los planes del equipo de Dart para el lanzamiento de null safety. También explica qué queremos decir con sound null safety y **por qué eso es diferente de los enfoques que muchos otros lenguajes toman.

¿Por qué null safety?

#

Dart es un lenguaje con seguridad de tipos. Esto significa que cuando obtienes una variable de algún tipo, el compilador puede garantizar que es de ese tipo. Pero la seguridad de tipos por sí sola no garantiza que la variable no sea null.

Los errores de null son muy comunes. Una búsqueda en GitHub lleva a miles de issues causados por nulls en código Dart, y aún más miles de commits intentando arreglar esos issues.

Intenta ver si puedes detectar los problemas de nulabilidad en el siguiente ejemplo de código:

void printLengths(List<File> files) {
  for (var file in files) {
    print(file.lengthSync());
  }
}

Esta función ciertamente fallará si se llama con null, pero hay un segundo caso a considerar:

void main() {
  // Error case 1: passing a null to files.
  printLengths(null);

  // Error case 2: passing list of files, containing a null item.
  printLengths([File('filename1'), File('filename2'), null]);
}

La función null safety hace que este problema desaparezca:

DartPad con null safety mostrando errores por pasar null a una lista de files e incluir un elemento null en la lista.

Con null safety, puedes razonar sobre tu código con más confianza. No más molestos errores de desreferenciación de null en tiempo de ejecución. En su lugar, obtienes errores estáticos mientras programas.

Sound null safety

#

El null safety de Dart es sound. Esto significa que Dart está 100% seguro de que la lista files, y los elementos en ella, no pueden ser null en el ejemplo anterior. Cuando Dart analiza tu código y determina que una variable es no-nullable, esa variable es siempre no-nullable: si inspeccionas tu código en ejecución en el debugger, verás que la no-nulabilidad se mantiene en tiempo de ejecución. Por el contrario, algunas otras implementaciones son no sólidas, y en muchos casos todavía necesitan realizar verificaciones de null en tiempo de ejecución. Dart comparte sound null safety con Swift, pero no con muchos otros lenguajes de programación.

La solidez del null safety de Dart tiene otra implicación bienvenida: significa que tus programas pueden ser más pequeños y rápidos. Porque Dart está realmente seguro de que files nunca es null, Dart puede optimizar. Por ejemplo, el compilador ahead-of-time (AOT) de Dart puede producir código nativo más pequeño y rápido, porque no necesita añadir verificaciones de null cuando sabe que una variable no es null.

Hemos visto algunos resultados preliminares muy prometedores. Por ejemplo, vimos una mejora de rendimiento del 19% en un microbenchmark que emula patrones típicos de renderizado del framework de Flutter.

Principios de diseño

#

Antes de comenzar el diseño detallado de null safety, el equipo de Dart definió los siguientes tres principios centrales:

  • No-nullable por defecto. A menos que le digas explícitamente a Dart que una variable puede ser null, la considerará no-nullable. Elegimos esto como predeterminado porque encontramos que non-null era con diferencia la elección más común en las APIs.

  • Adoptable incrementalmente. Hay mucho código Dart por ahí. Debe ser posible migrar a null safety incrementalmente, parte por parte. Debe ser posible tener código null-safe y código no-null-safe en el mismo proyecto. También proporcionaremos herramientas para ayudarte con la migración.

  • Totalmente sólida. Como se mencionó arriba, el null safety de Dart es sólido. Una vez que migres todo tu proyecto y tus dependencias a null safety, obtienes todos los beneficios de la solidez.

Declarar variables con null safety

#

La sintaxis central es suficientemente simple. Aquí hay algunas variables no-nullable, declaradas de diferentes maneras. Recuerda, no-nullable es el valor predeterminado, así que estas declaraciones se ven como se ven hoy, pero su significado cambia.

// In null-safe Dart, none of these can ever be null.
var i = 42;
final b = Foo();
String m = '';

Dart se asegurará de que nunca asignes null a ninguna de las variables anteriores. Si intentas hacer i = null mil líneas más adelante, obtendrás un error de análisis estático y líneas rojas onduladas, y tu programa se negará a compilar.

Si quieres que tu variable sea nullable, puedes usar ?, así:

// These are all nullable variables.
int? j = 1;  // Can be null later.
final Foo? c = getFoo();  // Maybe the function returns null.
String? n;  // Is null at first. Can be null at any later time, too.

Las variables anteriores se comportan de la misma manera que todas las variables lo hacen hoy.

Puedes usar la sintaxis ? en otros lugares también:

// In function parameters.
void boogie(int? count) {
  // It's possible that count is null.
}

// In function return values.
Foo? getFoo() {
  // Can return null instead of Foo.
}

// Also: generics, typedefs, type checks, etc.
// And any combination of the above.

Pero, una vez más, el sueño es que casi nunca tengas que usar ?. La gran mayoría de tus tipos serán no-nullable.

Hacer null safety más fácil de usar

#

El equipo de Dart está esforzándose mucho para hacer null safety lo más fácil de usar posible. Por ejemplo, mira este código, que usa if para verificar un valor null:

void honk(int? loudness) {
  if (loudness == null) {
    // No loudness specified, notify the developer
    // with maximum loudness.
    _playSound('error.wav', volume: 11);
    return;
  }

  // Loudness is non-null, let's just clamp it to acceptable levels.
  _playSound('honk.wav', volume: loudness.clamp(0, 11));
}

Nota cómo Dart es lo suficientemente inteligente para darse cuenta de que para cuando pasamos esa declaración if, la variable loudness no puede ser null. Y así Dart nos deja llamar al método clamp() sin hacer malabares. Esta conveniencia está habilitada por algo llamado análisis de flujo: el analyzer de Dart recorre tu código como si lo estuviera ejecutando, descubriendo información adicional sobre tu código automáticamente.

Aquí hay otro ejemplo, que muestra un caso donde Dart puede estar seguro de que una variable es non-null porque siempre le asignamos un valor non-null:

int sign(int x) {
  // The result is non-nullable.
  int result;

  if (x >= 0) {
    result = 1;
  } else {
    result = -1;
  }

  // By this point, Dart knows the result cannot be null.
  return result;
}

Si eliminas cualquiera de las asignaciones anteriores (por ejemplo, borrando la línea result = -1;), Dart no puede garantizar que result sea non-null: obtendrás un error estático y tu código no compilará.

El análisis de flujo solo funciona dentro de las funciones. Si tienes una variable global o un campo de clase, entonces Dart no puede garantizar cuándo se le asignará qué valor. Dart no puede modelar el flujo de toda tu aplicación. Por esa razón, puedes usar la nueva palabra clave late cuando sabes que una variable será non-null antes de que la leas por primera vez, pero no puedes inicializarla inmediatamente.

class Goo {
  late Viscosity v;

  Goo(Material m) {
    v = m.computeViscosity();
  }
}

Ten en cuenta que v es non-null, aunque comienza sin inicializar. Dart confía en que no intentarás leer v antes de que se le asigne un valor non-null, y tu código compila sin errores.

Null safety es retrocompatible

#

El equipo de Dart ha estado trabajando durante más de un año para llevar null safety a la preview técnica. Es la mayor adición al lenguaje Dart desde que introdujimos Dart 2. Sin embargo, no es un cambio disruptivo. El código existente puede llamar a código que usa null safety, y viceversa. Incluso después de que null safety esté disponible, será una función opcional que puedes adoptar cuando estés listo. Tu código existente continuará funcionando sin cambios.

Recientemente migramos las bibliotecas centrales de Dart para usar completamente null safety. Como ejemplo de la retrocompatibilidad de null safety, reemplazamos las bibliotecas centrales existentes sin ninguna rotura en las pruebas y apps de prueba existentes que se ejecutan en los entornos de prueba de Dart y Flutter. Incluso implementamos las nuevas bibliotecas centrales a nuestros muchos clientes internos de Google, directamente en sus bases de código de producción, sin problemas. Planeamos migrar todos nuestros paquetes y apps para usar null safety cuando la función se lance, y esperamos que tú hagas lo mismo. Pero podrás hacerlo a tu ritmo, paquete por paquete, app por app.

La hoja de ruta de null safety

#

Planeamos lanzar null safety gradualmente, en tres pasos:

  1. Preview técnica. Esto se lanza hoy, y está disponible en el canal dev de Dart. Consulta la sección "Pruébalo ahora" a continuación para más detalles. Las cosas todavía están sujetas a cambios, así que no uses null safety en código de producción todavía. ¡Pero prueba las cosas y danos tu feedback!

  2. Lanzamiento beta. Null safety estará disponible en el canal beta de Dart, y ya no detrás de un flag experimental. La función estará muy cerca de la versión final esperada. Si eres dueño de un paquete o plugin en pub.dev, este es el momento en que puedes comenzar a migrar, pero no deberías publicarlo como versión estable todavía.

  3. Lanzamiento estable. Todos tendrán null safety, y se te animará a publicar tus paquetes y plugins migrados como versiones estables. También deberías migrar tus apps de producción.

Si todo va bien, planeamos lanzar null safety como una función estable antes de que termine el año. Entre ahora y entonces, añadiremos herramientas para ayudarte a hacer tu código null safe, incluyendo:

  • Una herramienta de migración para apoyarte en la automatización de muchos de los pasos para actualizar paquetes y apps existentes

  • Etiquetas en pub.dev, para que puedas saber si un paquete soporta null safety o no

  • Una extensión del comando pub outdated, con soporte para encontrar las últimas versiones de tus dependencias que soportan null safety

Pruébalo ahora

#

La forma más rápida de probar null safety hoy es a través de nullsafety.dartpad.dev, una versión de DartPad con null safety habilitado. Abre el menú desplegable Learn with Snippets para encontrar una serie de ejercicios de aprendizaje que recorren la nueva sintaxis y los conceptos básicos de null safety.

DartPad con el experimento de null safety habilitado y fragmentos de código interactivos.

También puedes probar null safety en pequeñas apps de línea de comandos. (Todavía no hemos migrado frameworks grandes como Flutter.) Comienza descargando una copia del SDK de Dart del canal dev, y luego obtén una copia de esta app de CLI de Dart de ejemplo (repositorio de GitHub, archivo zip). El archivo README de la app de ejemplo tiene instrucciones para ejecutar la app con el flag experimental de null safety. Otros archivos en el ejemplo proporcionan configuraciones de lanzamiento que habilitan la depuración en VS Code y Android Studio.

También tenemos documentación, con planes de producir más:

Estamos muy felices de traer null safety a Dart. Sound null safety es una función distintiva de Dart que te ayuda a escribir código menos propenso a errores y obtener mejor rendimiento. Esperamos que experimentes con la función en la preview técnica, y nos des tu feedback a través de nuestro issue tracker. ¡Feliz programación!

Más de Dart