Anunciando Dart 2.12
Sound null safety y Dart FFI llegan al canal estable.
Hoy anunciamos Dart 2.12, que incluye versiones estables de sound null safety y Dart FFI. Null safety es nuestra última gran característica de productividad, diseñada para ayudarte a evitar errores de null, una clase de bugs que a menudo son difíciles de detectar, como se detalla en esta introducción en video. FFI es un mecanismo de interoperabilidad que te permite invocar código existente escrito en el lenguaje de programación C, como llamar a las APIs de Win32 de Windows. Dart 2.12 está disponible hoy.
Las capacidades únicas de la plataforma Dart
#Antes de ver sound null safety y FFI en detalle, hablemos de cómo encajan en nuestros objetivos con la plataforma Dart. Los lenguajes de programación tienden a compartir muchas capacidades. Por ejemplo, muchos lenguajes soportan programación orientada a objetos o ejecutarse en la web. Lo que realmente distingue a los lenguajes es su combinación única de capacidades.
Las capacidades únicas de Dart abarcan tres dimensiones:
-
Portable: Compiladores eficientes generan código de máquina x86 y ARM para dispositivos, y JavaScript optimizado para la web. Una amplia gama de targets está soportada — dispositivos móviles, PCs de escritorio, backends de apps y más. Un conjunto extenso de librerías y paquetes proporciona APIs consistentes que funcionan en todas las plataformas, reduciendo aún más el costo de crear verdaderas apps multiplataforma.
-
Productivo: La plataforma Dart soporta hot reload, habilitando un desarrollo rápido e iterativo tanto para dispositivos nativos como para la web. Y Dart ofrece construcciones ricas como isolates y async/await para manejar patrones comunes de apps concurrentes y orientadas a eventos.
-
Robusto: El sistema de tipos sound y null-safe de Dart detecta errores durante el desarrollo. Y la plataforma en general es altamente escalable y confiable, con uso en producción por más de una década por una gran variedad de apps, incluyendo apps críticas para el negocio como Google Ads y Google Assistant.
Sound null safety hace que el sistema de tipos sea aún más robusto, y habilita mejor rendimiento. Dart FFI te permite usar librerías de C existentes para mejor portabilidad, y te da la opción de usar código C altamente optimizado para tareas críticas de rendimiento.
Sound null safety
#Sound null safety es la mayor adición al lenguaje Dart desde la introducción del sistema de tipos sound en Dart 2.0. Null safety fortalece aún más el sistema de tipos, permitiéndote detectar errores de null, que son una causa común de crashes en apps. Al optar por null safety, puedes detectar errores de null durante el desarrollo, previniendo crashes en producción.
Sound null safety fue diseñado en torno a unos pocos principios core. Revisemos cómo estos principios te impactan como desarrollador.
No-nullable por defecto: Un cambio fundamental al sistema de tipos
#El desafío core antes de null safety era que no podías distinguir entre código que anticipaba recibir null y código que no funcionaba con nulls. Hace unos meses descubrimos un bug en el canal master de Flutter donde varios comandos de la herramienta flutter crasheaban en ciertas configuraciones de máquina con un error de null:
The method '>=' was called on null. El problema subyacente era código como este:
final int major = version?.major;
final int minor = version?.minor;
if (globals.platform.isMacOS) {
// plugin path of Android Studio changed after version 4.1.
if (major >= 4 && minor >= 1) {
...
¿Puedes detectar el error? Como version puede ser null, tanto major como minor
también pueden ser null. Este bug podría parecer fácil de detectar aquí de forma aislada, pero en la práctica código como este se cuela todo el tiempo, incluso con un proceso riguroso de revisión de código como el que se usa en el repo de Flutter. Con null safety, el análisis estático detecta este issue inmediatamente. (Pruébalo en vivo en DartPad.)
Ese era un error bastante simple. Durante nuestro uso temprano de null safety en código internamente en Google, hemos visto que se detectan errores mucho más complejos. Algunos de estos eran bugs conocidos desde hace años, pero donde los equipos no habían podido encontrar la causa sin las verificaciones estáticas adicionales de null safety. Aquí hay algunos ejemplos:
-
Un equipo interno descubrió que a menudo estaban verificando valores null en expresiones que nunca podían ser null. Este problema se veía con mayor frecuencia en código que usaba protobuf, donde los campos opcionales devuelven un valor por defecto cuando no están establecidos, y nunca null. Como resultado, el código verificaba incorrectamente la condición por defecto, confundiendo valores por defecto con valores null.
-
El equipo de Google Pay encontró bugs en su código de Flutter donde fallaban al intentar acceder a objetos
Statede Flutter fuera del contexto de unWidget. Antes de null safety, esos objetos devolvían null y enmascaraban el error; con null safety, el análisis sound determinó que esas propiedades nunca podían ser null, y lanzó un error de análisis. -
El equipo de Flutter encontró un bug donde el engine de Flutter podía crashear si se pasaba
nullal parámetrosceneenWindow.render(). Durante la migración a null safety, añadieron un hint para marcar Scene como no-nullable, y luego pudieron prevenir fácilmente potenciales crashes de la app que null habría provocado.
Trabajando con no-nullable por defecto
#Una vez que habilitas null safety, lo básico de las declaraciones de variables cambia porque los tipos por defecto son no-nullable:
// In null-safe Dart, none of these can ever be null.
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo();
Si quieres crear una variable que pueda contener tanto un valor como null, necesitas hacerlo explícito en la declaración de la variable añadiendo un sufijo
? al tipo:
// aNullableInt can hold either an integer or null.
int? aNullableInt = null;
La implementación de null safety es robusta, con un rico análisis de flujo estático que facilita trabajar con tipos nullable. Por ejemplo, después de verificar null, Dart promueve el tipo de una variable local de nullable a no-nullable:
int definitelyInt(int? aNullableInt) {
if (aNullableInt == null) {
return 0;
}
// aNullableInt has now promoted to a non-null int.
return aNullableInt;
}
También hemos añadido una nueva palabra clave, required. Cuando un parámetro nombrado se marca como required
(lo cual sucede mucho en las APIs de widgets de Flutter) y el llamador olvida proporcionar el argumento, se produce un error de análisis:
Migración incremental a null safety
#Como null safety es un cambio tan fundamental a nuestro sistema de tipos, sería extremadamente disruptivo si insistiéramos en una adopción forzada. Así que tú decidas cuándo es el momento adecuado, null safety es una característica opt-in: puedes usar Dart 2.12 sin verse obligado a habilitar null safety. Incluso puedes depender de paquetes que ya han habilitado null safety, independientemente de si tu app o paquete ha habilitado null safety o no.
Para ayudarte a migrar código existente a null safety, proporcionamos una herramienta de migración y una guía de migración. La herramienta comienza analizando todo tu código existente. Luego puedes revisar interactivamente las propiedades de nullability que la herramienta ha inferido. Si no estás de acuerdo con alguna de las conclusiones de la herramienta, puedes añadir hints de nullability para cambiar la inferencia. Añadir algunos hints de migración puede tener un gran impacto en la calidad de la migración.
Por ahora, los nuevos paquetes y apps creados con dart create
y flutter create no habilitan sound null safety. Esperamos cambiar eso en un futuro release estable, cuando veamos que la mayor parte del ecosistema ha migrado. Puedes
habilitar null safety fácilmente en un paquete o app recién creado usando
dart migrate.
Estado de la migración a null safety del ecosistema de Dart
#Durante el último año hemos ofrecido varias previews y betas de sound null safety, con el objetivo de sembrar el ecosistema con paquetes que soporten null safety. Esta preparación es importante, ya que recomendamos migrar a sound null safety en orden — no deberías migrar un paquete o app antes de que todas sus dependencias ya hayan migrado.
Hemos publicado versiones null-safe de cientos de paquetes ofrecidos por los equipos de Dart, Flutter, Firebase y Material. Y hemos visto un gran soporte por parte del increíble ecosistema de Dart y Flutter, de modo que pub.dev ahora tiene más de mil paquetes que soportan null safety. Es importante destacar que los paquetes más populares migraron primero, de modo que el 98% de los top-100 paquetes más populares, el 78% de los top-250, y el 57% de los top-500 ya soportan null safety a tiempo para el lanzamiento de hoy. Esperamos ver aún más paquetes con null safety en pub.dev en las próximas semanas. Nuestro análisis muestra que la gran mayoría de los paquetes en pub.dev ya están desbloqueados y pueden comenzar la migración.
Los beneficios de sound null safety completo
#Una vez que hayas migrado completamente, el null safety de Dart es sound. Esto significa que Dart está 100% seguro de que las expresiones que tienen un tipo no-nullable no pueden ser null. Cuando Dart analiza tu código y determina que una variable es no-nullable, esa variable es siempre no-nullable. 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 más rápidos. Como Dart está seguro de que las variables no-nullable nunca son null, Dart puede optimizar. Por ejemplo, el compilador ahead-of-time (AOT) de Dart puede producir código nativo más pequeño y más rápido, porque no necesita añadir verificaciones de null cuando sabe que una variable no es null.
Dart FFI para integrar Dart con librerías de C
#Dart FFI te permite aprovechar código existente en librerías de C, tanto para mejor portabilidad como para integrar código C altamente optimizado para tareas críticas de rendimiento. A partir de Dart 2.12, Dart FFI está fuera de la fase de beta y ahora se considera estable y listo para uso en producción. También hemos añadido algunas características nuevas, incluyendo structs anidados y pasar structs por valor.
Pasar structs por valor
#Las structs pueden pasarse tanto por referencia como por valor en código C. FFI previamente solo soportaba pasar por referencia, pero a partir de Dart 2.12, puedes pasar structs por valor. Aquí hay un pequeño ejemplo de dos funciones de C que pasan tanto por referencia como por valor:
struct Link {
double value;
Link* next;
};
void MoveByReference(Link* link) {
link->value = link->value + 10.0;
}
Coord MoveByValue(Link link) {
link.value = link.value + 10.0;
return link;
}
Structs anidadas
#Las APIs de C usan frecuentemente structs anidadas — structs que a su vez contienen structs, como en este ejemplo:
struct Wheel {
int spokes;
};
struct Bike {
struct Wheel front;
struct Wheel rear;
int buildYear;
};
A partir de Dart 2.12, las structs anidadas están soportadas en FFI.
Cambios en la API
#Como parte de declarar FFI estable, y en soporte de las características anteriores, hemos hecho algunos cambios menores en la API.
Crear structs vacías ahora no está permitido (breaking change #44622) y produce una advertencia de deprecación. Puedes usar un nuevo tipo,
Opaque, para representar structs vacías. Las funciones de dart:ffi sizeOf,
elementAt y ref ahora requieren argumentos de tipo en tiempo de compilación (breaking change
#44621). Como se han añadido nuevas funciones de conveniencia en
package:ffi, no se requiere boilerplate adicional para asignar y liberar memoria en casos comunes:
// Allocate a pointer to an Utf8 array, fill it from a Dart string,
// pass it to a C function, convert the result, and free the arg.
//
// Before API change:
final pointer = allocate<Int8>(count: 10);
free(pointer);
final arg = Utf8.toUtf8('Michael');
var result = helloWorldInC(arg);
print(Utf8.fromUtf8(result);
free(arg);
// After API change:
final pointer = calloc<Int8>(10);
calloc.free(pointer);
final arg = 'Michael'.toNativeUtf8();
var result = helloWorldInC(arg);
print(result.toDartString);
calloc.free(arg);
Generación automática de bindings FFI
#Para grandes superficies de API puede ser muy lento escribir los bindings de Dart que se integran con el código C. Para reducir esta carga, hemos construido un generador de bindings para crear automáticamente wrappers FFI a partir de archivos header de C. Te invitamos a probarlo:
package:ffigen.
Hoja de ruta de FFI
#Con la plataforma core de FFI completada, estamos enfocando en extender el conjunto de características de FFI con características que se apoyan sobre la plataforma core. Algunas de las características que estamos investigando incluyen:
-
Tipos de datos específicos de ABI como int, long, size_t (#36140)
Arrays inline en structs (#35763)
Structs packed (#38158)
Tipos union (#38491)
-
Exponer finalizers a Dart (#35770; sin embargo ten en cuenta que ya puedes usar finalizers desde C)
Ejemplos de uso de FFI
#Hemos visto muchos usos creativos de Dart FFI para integrar con una gama de APIs basadas en C. Aquí hay algunos ejemplos:
-
open_file es una única API para abrir archivos en múltiples plataformas. Usa FFI para invocar APIs nativas del sistema operativo en Windows, macOS y Linux.
-
win32 envuelve las APIs de Win32 más comunes, haciendo posible llamar a una amplia gama de APIs de Windows directamente desde Dart.
-
objectbox es una base de datos rápida respaldada por una implementación basada en C.
-
tflite_flutter usa FFI para envolver la API de TensorFlow Lite.
¿Qué viene ahora para el lenguaje Dart?
#Sound null safety es el mayor cambio que hemos hecho al lenguaje Dart en varios años. A continuación vamos a hacer cambios más incrementales al lenguaje y la plataforma, construyendo sobre nuestra base sólida. Aquí hay un vistazo rápido de algunas de las cosas con las que estamos experimentando en nuestro language design funnel:
Type aliases (#65): La capacidad de crear alias de tipos para tipos que no son funciones. Por ejemplo, podrías crear un typedef y usarlo como tipo de variable:
typedef IntList = List<int>;
IntList il = [1,2,3];
Operador triple-shift (#120): Añadir un nuevo operador >>> totalmente sobrescribible para hacer desplazamiento de bits sin signo en enteros.
Anotaciones de metadatos genéricas (#1297): Extender las anotaciones de metadatos para soportar también anotaciones que contienen argumentos de tipo.
Meta-programming estático (#1482): Soporte para meta-programming estático — programas Dart que producen nuevo código fuente Dart durante la compilación, similar a las macros de Rust y los function builders de Swift. Esta característica aún está en una fase de exploración temprana, pero creemos que puede habilitar casos de uso que hoy dependen de generación de código.
Dart 2.12 está disponible ahora
#Dart 2.12, con sound null safety y FFI estable, está disponible hoy en los SDKs de Dart 2.12 y Flutter 2.0. Por favor tómate un momento para revisar los issues conocidos de null safety para Dart y para Flutter. Si encuentras algún otro issue, por favor repórtalo en el issue tracker de Dart.
Si has desarrollado paquetes publicados en pub.dev, por favor revisa la guía de migración hoy, y aprende cómo migrar a sound null safety. Migrar tu paquete probablemente ayude a desbloquear otros paquetes y apps que dependen de él. ¡También nos gustaría extender un gran agradecimiento a los que ya migraron!
Nos encantaría escuchar sobre tu experiencia tanto con sound null safety como con FFI. Deja un comentario abajo o twittea a @dart_lang.