Glosario
Una referencia de glosario para la terminología utilizada en dart.dev.
Las siguientes son definiciones de términos utilizados en la documentación de Dart.
AOT
La compilación Ahead-of-Time (AOT) produce binarios nativos optimizados en tiempo de compilación.
La compilación Ahead-of-Time (AOT) convierte el código Dart en código de máquina nativo en tiempo de compilación. Esto da como resultado ejecutables más pequeños y rápidos que se inician instantáneamente y se ejecutan con un rendimiento constante. Las compilaciones AOT se someten a tree-shaking para eliminar el código no utilizado y se utilizan para desplegar aplicaciones de producción.
Documentos y recursos relacionados
Paquete de aplicación
Un paquete de Dart que contiene una aplicación ejecutable.
Un paquete de aplicación es un paquete de Dart que contiene un programa o aplicación con un punto de entrada principal. Está destinado a ser ejecutado directamente, ya sea en la línea de comandos, en un navegador o mediante otro embedder, como el proporcionado por Flutter.
Los paquetes de aplicación pueden tener dependencias de otros paquetes, pero nunca se depende de ellos mismos. A diferencia de los paquetes normales, no están destinados a ser compartidos.
Los paquetes de aplicación deben registrar sus lockfiles en el control de código fuente, de modo que todas las personas que trabajen en la aplicación y cada lugar donde se despliegue la aplicación tengan un conjunto consistente de dependencias.
Documentos y recursos relacionados
Assist
Una edición de código local y automatizada orientada a realizar mejoras comunes en el código.
Un assist es una edición de código local y automatizada orientada a realizar mejoras comunes
en el código.
Algunos ejemplos de assists incluyen convertir sentencias switch en expresiones switch,
invertir los bloques then y else en una sentencia
if,
e insertar widgets en una estructura de widgets.
Documentos y recursos relacionados
Asíncrono
Paradigma de programación en Dart donde las operaciones como E/S o llamadas de red se completan más tarde sin bloquear el bucle de eventos.
Dart utiliza un modelo de bucle de eventos (event loop) de un solo hilo para la programación asíncrona, lo que permite la ejecución no bloqueante de tareas que consumen mucho tiempo, como la E/S de archivos, solicitudes HTTP y temporizadores. A diferencia del código síncrono que se bloquea hasta su finalización, las operaciones async se encolan a través de microtareas/eventos, manteniendo las interfaces de usuario receptivas.
Los componentes clave incluyen:
- Futures:
Representan un valor disponible más tarde, creado por APIs como
http.get(). - async/await: Palabras clave de Dart para escribir código asíncrono legible y fácil de mantener.
- Streams: Manejan secuencias de eventos async, como datos de web sockets.
- Isolates: Permiten el paralelismo real a través de contextos de ejecución independientes con paso de mensajes.
Tipo inferior
Un tipo que no tiene valores y es un subtipo de todos los demás tipos.
El tipo inferior (bottom type) en Dart es el tipo que no tiene valores y se considera un subtipoSubtipoUn tipo que se puede usar en cualquier lugar donde se espere un valor de su supertipo. Aprende más de cualquier otro tipo.
En Dart, el tipo inferior está representado por el tipo Never.
Esto significa que un valor de tipo Never se puede usar en cualquier lugar,
porque tal valor nunca puede existir en realidad.
Se utiliza con mayor frecuencia como el tipo de retorno de funciones para
indicar que nunca retornan, como por ejemplo aquellas que
lanzan excepciones o entran en un bucle infinito.
Por ejemplo, la siguiente función fail siempre lanza una excepción,
por lo que se declara con un tipo de retorno Never para
indicar que nunca retorna:
Never fail(String message) {
throw Exception(message);
}
void main() {
String result = fail('Oops'); // OK: Never is a subtype of String.
}
Dado que fail nunca retorna, se permite asignarlo a un String.
Documentos y recursos relacionados
Callback
Una función que se pasa como argumento a otra función para ser llamada más tarde.
Un callback es una función que pasas como argumento a otra función para que pueda ser invocada (o "llamada de vuelta") en un momento posterior.
Los callbacks se utilizan a menudo para:
- Manejo de eventos.
- Personalizar el comportamiento de las APIs.
- Operaciones asíncronas, como responder al resultado de un future.
Por ejemplo, en el siguiente fragmento, la función printResult se pasa
como un callback a doOperation, y se llama después de que finaliza la operación:
void doOperation(int a, int b, void Function(int) callback) {
final result = a + b;
callback(result);
}
void printResult(int value) {
print('The result is $value.');
}
void main() {
doOperation(3, 4, printResult); // Prints: The result is 7.
}
Documentos y recursos relacionados
Closurización
El proceso de convertir un método o función en un closure.
La closurización es el proceso donde Dart convierte un método o función en un objeto de función (o tear-off) que se puede almacenar, pasar o invocar más tarde.
Un tear-off es una referencia a una función o método existente que se evalúa como un objeto de función para esa entidad, pero no la llama. Cuando haces un tear-off de una función, el entorno de ejecución (o compilador) la envuelve en una clausura para que se comporte como cualquier otro objeto de función en Dart.
Ejemplo:
class Greeter {
void greet(String name) {
print('Hello, $name!');
}
}
void main() {
final greeter = Greeter();
// Tear-off of the instance method `greet`.
final sayHello = greeter.greet;
sayHello('Dart'); // Prints: Hello, Dart!
}
En este ejemplo, greeter.greet se closuriza en
un objeto de función que captura la instancia greeter.
Cuando llamas a sayHello, se ejecuta como si
hubieras llamado a greeter.greet(...) directamente.
La closurización garantiza que los tear-offs se puedan utilizar en cualquier lugar donde se espere una función.
Documentos y recursos relacionados
Asset de código
Código nativo compilado que se empaqueta con una aplicación Dart utilizando un build hook
y se puede utilizar a través de dart:ffi.
Un asset de código es código compilado que no está escrito en Dart pero
puede ser empaquetado y utilizado por una aplicación Dart.
Los ejemplos incluyen librerías compartidas (.so, .dll, .dylib) construidas
en lenguajes como C, C++ o Rust.
Los assets de código se pueden empaquetar con un paquete Dart utilizando un build hook. El código Dart puede realizar llamadas a estos assets utilizando FFI.
Históricamente, los assets de código también se denominaban como assets nativos o assets de código nativo.
Los assets de código son útiles para:
- Integrarse con librerías nativas existentes.
- Acceder a capacidades del sistema que no están expuestas directamente en Dart.
- Compartir funcionalidades críticas para el rendimiento entre plataformas.
Por ejemplo, una aplicación Dart podría incluir una librería nativa .so que expone
funciones específicas, y llamar a esas funciones desde Dart.
Documentos y recursos relacionados
Combinador
Una cláusula de palabra clave que limita o modifica lo que se importa o exporta.
En Dart, un combinador es una cláusula que
sigue a una directiva import o export para limitar o modificar
el conjunto de nombres traídos al ámbito.
Dart admite dos tipos de combinadores:
show— incluye explícitamente nombres específicos.hide— excluye nombres específicos.
Los combinadores ayudan a controlar la contaminación del espacio de nombres y evitar conflictos cuando varias librerías definen símbolos con el mismo nombre.
Ejemplo usando show:
import 'dart:math' show pi, sqrt;
void main() {
print(pi); // Accessible
print(sqrt(9)); // Accessible
// print(Random()); // Error: Random is not imported.
}
Ejemplo usando hide:
import 'dart:math' hide pi;
void main() {
// print(pi); // Error: pi is hidden.
print(Random()); // Accessible
}
Documentos y recursos relacionados
Contexto constante
Una región de código donde la palabra clave const está implícita y todo lo que está dentro de esa región debe ser una constante.
Un contexto constante es una región de código en la que no
es necesario incluir la palabra clave const porque está implícita por
el hecho de que todo en esa región debe ser una constante.
Las siguientes ubicaciones son contextos constantes:
-
Todo lo que está dentro de un literal de lista, mapa o conjunto que tiene como prefijo la palabra clave
const. Por ejemplo:dartvar l = const [/*constant context*/]; -
Los argumentos dentro de una invocación de un constructor constante. Por ejemplo:
dartvar p = const Point(/*constant context*/); -
El inicializador de una variable que tiene como prefijo la palabra clave
const. Por ejemplo:dartconst v = /*constant context*/; Anotaciones.
-
La expresión en una cláusula
case. Por ejemplo:dartvoid f(int e) { switch (e) { case /*constant context*/: break; } }
Documentos y recursos relacionados
Tipo de contexto
El tipo que el código circundante espera de una expresión.
El tipo de contexto es el tipo que el código circundante espera de una expresión, como el tipo de una variable, el tipo de un parámetro o un tipo de retorno.
Dart utiliza el tipo de contexto para interpretar e inferir el significado de las expresiones, incluyendo:
-
Inferencia de tipos ("inferencia hacia abajo"):
dartList<int> list = [];El tipo de contexto
List<int>permite al compilador inferir el tipo de lista como<int>[]. -
Implicit downcast:
dartString asString(dynamic value) => value;El contexto de retorno es
String, por lo que Dart inserta una conversión implícita hacia abajo. -
Literal interpretation:
dartdouble d = 0;El tipo de contexto
doublehace que0se comporte como0.0. -
Atajo de acceso estático (atajo de punto):
dartint x = .parse(input);El tipo de contexto es
int, por lo que.parsese resuelve comoint.parse(input).
Algunas expresiones no tienen tipo de contexto, incluyendo:
-
Cuando se usan como sentencias:
Las expresiones comoset.remove(value);se utilizan únicamente por su efecto, no por su valor, por lo que no se espera ningún tipo. -
Cuando el tipo de contexto se infiere de la expresión:
Por ejemplo, envar list = [1];, el literal de lista no tiene tipo de contexto. Dart infiereList<int>a partir del contenido y asigna ese tipo a la variable.
Documentos y recursos relacionados
Restricción del Dart SDK
Las versiones de Dart que un paquete admite.
El rango de versiones del Dart SDK que un paquete declara admitir.
Una restricción de SDK se especifica utilizando la sintaxis de restricción de versión
normal,
pero en una sección especial environment en el pubspec.
Documentos y recursos relacionados
Asignación definitiva
La determinación de si a una variable se le ha asignado definitivamente un valor antes de ser utilizada.
El análisis de asignación definitiva es el proceso de determinar, para cada variable local en cada punto del código, cuál de las siguientes opciones es verdadera:
- A la variable se le ha asignado definitivamente un valor (definitivamente asignada).
- A la variable definitivamente no se le ha asignado un valor (definitivamente no asignada).
- Es posible que a la variable se le haya asignado o no un valor, dependiendo de la ruta de ejecución tomada para llegar a ese punto.
El análisis de asignación definitiva ayuda a encontrar problemas en el código, como lugares donde se hace referencia a una variable a la que podría no habérsele asignado un valor, o lugares donde una variable a la que solo se le puede asignar un valor una vez se le está asignando después de que ya se le haya asignado un valor.
Por ejemplo, en el siguiente código, la variable s definitivamente
no está asignada cuando se pasa como argumento a print:
void f() {
String s;
print(s);
}
Pero en el siguiente código, la variable s está definitivamente asignada:
void f(String name) {
String s = 'Hello $name!';
print(s);
}
El análisis de asignación definitiva incluso puede determinar si
una variable está definitivamente asignada (o no asignada) cuando
hay múltiples rutas de ejecución posibles.
En el siguiente código, la función print se llama si
la ejecución pasa por la rama verdadera o falsa de la
sentencia if, pero debido a que s se asigna sin importar qué rama se tome,
está definitivamente asignada antes de pasarse a print:
void f(String name, bool casual) {
String s;
if (casual) {
s = 'Hi $name!';
} else {
s = 'Hello $name!';
}
print(s);
}
En el análisis de flujo, el final de la sentencia if se denomina
join: un lugar donde dos o más rutas de ejecución se vuelven a unir.
Cuando hay un join, el análisis dice que
una variable está definitivamente asignada si está definitivamente asignada a lo largo
de todas las rutas que se están uniendo, y definitivamente no asignada si
definitivamente no está asignada a lo largo de todas las rutas.
A veces, a una variable se le asigna un valor en una ruta pero no en otra,
en cuyo caso la variable podría o no haber sido asignada.
En el siguiente ejemplo, la rama verdadera de la sentencia if podría
ejecutarse o no, por lo que la variable podría o no estar asignada:
void f(String name, bool casual) {
String s;
if (casual) {
s = 'Hi $name!';
}
print(s);
}
Lo mismo ocurre si hay una rama falsa que
no asigna un valor a s.
El análisis de los bucles es un poco más complicado,
pero sigue el mismo razonamiento básico.
Por ejemplo, la condición en un bucle while siempre se ejecuta,
pero el cuerpo podría ejecutarse o no. Así que, al igual que una sentencia if,
hay un join al final de la sentencia while entre
la ruta en la que la condición es true y
la ruta en la que la condición es false.
Documentos y recursos relacionados
Dependencia
Un paquete de Dart del cual depende otro paquete.
Una dependencia es cualquier otro paquete de Dart del cual depende un paquete. Si tu paquete desea importar código de algún otro paquete, ese paquete debe ser una dependencia tuya primero. Las dependencias se especifican en el archivo pubspec de tu paquete con la sintaxis descrita en Dependencias del paquete.
Para ver las dependencias utilizadas por un paquete, usa pub deps.
Documentos y recursos relacionados
Gráfico de dependencias
Una representación de cómo los componentes de un sistema dependen unos de otros.
Un gráfico de dependencias es un gráfico dirigido que muestra las relaciones entre los diferentes componentes de un sistema, como archivos, módulos, paquetes, funciones o assets. Cada nodo representa un componente y cada arista representa una dependencia entre dos componentes.
En Dart y Flutter, los gráficos de dependencias son utilizados comúnmente por herramientas como:
- El compilador, para determinar el orden de compilación.
- El gestor de paquetes, para resolver las versiones de los paquetes.
- Sistemas de compilación, para detectar qué necesita ser reconstruido.
- Tree shaking, para eliminar código no utilizado.
Por ejemplo, si el archivo main.dart importa a utils.dart, y utils.dart
a su vez
importa a math.dart, el gráfico de dependencias ayuda al compilador a comprender el
orden correcto para procesar estos archivos y qué partes del programa se utilizan realmente.
Los gráficos de dependencias son esenciales para:
- Compilaciones eficientes
- Eliminación de código muerto (tree shaking)
- Resolución de dependencias
- Compilación incremental
Documentos y recursos relacionados
Origen de la dependencia
Un tipo de lugar del cual pub puede obtener paquetes.
Un tipo de repositorio o ubicación de la cual pub puede recuperar paquetes. Un origen (source) no es un lugar específico como el sitio pub.dev o una URL de git específica. Cada origen describe un procedimiento general para acceder a un paquete.
Por ejemplo, git es uno de los orígenes de dependencia admitidos. El origen git sabe cómo descargar paquetes dada una URL de git. Varios orígenes admitidos diferentes están disponibles.
Documentos y recursos relacionados
Punto de entrada
Una librería de Dart que es invocada directamente por una implementación de Dart.
En el contexto general de Dart, un punto de entrada es
una librería de Dart que es invocada directamente por una implementación de Dart.
Por ejemplo, cuando pasas una librería de Dart como argumento de línea de comandos a
la Dart VM independiente, esa librería es el punto de entrada.
En otras palabras, suele ser el archivo .dart que contiene main().
En el contexto de pub, un paquete de punto de entrada o paquete raíz es la raíz de un gráfico de dependencias. Por lo general, será una aplicación. Cuando ejecutas tu aplicación, es el paquete de punto de entrada. Cualquier otro paquete del que dependa no será un punto de entrada en ese contexto.
Un paquete puede ser un punto de entrada en algunos contextos y no en otros.
Supongamos que tu aplicación utiliza un paquete A.
Cuando ejecutas tu aplicación, A no es el paquete de punto de entrada.
Sin embargo, si vas a A y ejecutas sus pruebas, en ese contexto,
sí es el punto de entrada ya que tu aplicación no está involucrada.
Documentos y recursos relacionados
Directorio del punto de entrada
Un directorio que contiene puntos de entrada de Dart.
Un directorio del punto de entrada es un directorio dentro de tu paquete de Dart que tiene permitido contener puntos de entrada de Dart.
pub tiene una lista de estos directorios: benchmark, bin, example,
test, tool y web (y lib, para
aplicaciones Flutter).
Cualquier subdirectorio de estos (excepto bin) también puede contener puntos de entrada.
Documentos y recursos relacionados
Function
Un término general para referirse a funciones de nivel superior, funciones locales, métodos estáticos y métodos de instancia.
Documentos y recursos relacionados
Getter
Un método especial que proporciona acceso de lectura a las propiedades de un objeto.
Los getters y setters son métodos especiales que
proporcionan acceso de lectura y escritura a las propiedades de un objeto.
Recuerda que cada variable de instancia tiene un getter implícito,
además de un setter implícito si corresponde.
Puedes crear propiedades adicionales implementando getters y setters
utilizando las palabras clave get y set.
Documentos y recursos relacionados
Dependencia directa
Una dependencia que un paquete de Dart utiliza directamente.
Una dependencia directa (immediate dependency) es una dependencia que un
paquete utiliza directamente y declara por sí mismo.
Las dependencias que enumeras en tu archivo pubspec.yaml
son
las dependencias directas de tu paquete.
Todas las demás dependencias son dependencias transitivas.
Documentos y recursos relacionados
Inmutable
Un objeto cuyo estado, incluyendo todos los valores anidados, no se puede cambiar después de que se crea.
Un objeto inmutable es aquel cuyo estado no se puede modificar después de que se crea.
Cuando un objeto es inmutable, todos sus campos deben ser final (no se pueden reasignar) y
los valores de esos campos deben ser inmutables por sí mismos (no se pueden mutar).
Esto ayuda a garantizar la consistencia y permite un uso más seguro en código concurrente o reactivo.
En Dart, una clase es inmutable si tú:
- Declaras todos los campos como final, para que no puedan ser reasignados.
- Te aseguras de que los valores de los campos sean inmutables por sí mismos.
- Opcionalmente, usa la anotación
@immutabledel paquete meta. Esto permite que el analyzer te advierta si algún campo no es final o hace referencia a un tipo mutable.
Además, todos los valores const de Dart son inmutables. Por ejemplo, const [1, 2, 3]
crea una lista inmutable. Si una clase tiene un constructor const (que no sea de fábrica), entonces
todos sus campos deben ser final.
Ejemplo:
import 'package:meta/meta.dart';
@immutable
class User {
final String name;
final int age;
const User(this.name, this.age);
}
En el siguiente ejemplo, una vez creada, no puedes modificar la instancia de User.
Debes crear una nueva para cambiar cualquier dato.
Documentos y recursos relacionados
Instanciar
Crear una instancia concreta (objeto) de una clase.
Instanciar una clase significa crear un objeto del tipo de esa
clase en la memoria.
En Dart, haces esto llamando a un constructor,
como por ejemplo final myItem = Item().
Las clases abstractas no se pueden instanciar directamente.
Documentos y recursos relacionados
Interop
La capacidad de que el código Dart interactúe con código escrito en otros lenguajes.
Interop (abreviatura de interoperabilidad) se refiere a la capacidad de Dart para comunicarse y trabajar con código escrito en otros lenguajes de programación.
En Dart, interop se usa comúnmente para:
- Llamar a código nativo escrito en lenguajes como C, C++ o Rust.
- Interactuar con APIs específicas de la plataforma.
- Reutilizar librerías existentes que no están escritas en Dart.
Dart proporciona diferentes mecanismos de interop según la plataforma y el caso de uso. Por ejemplo, las aplicaciones de Dart pueden usar la Foreign Function Interface (FFI) para llamar a librerías nativas, o capas de interop específicas de la plataforma cuando se ejecutan en Flutter o en la web.
Documentos y recursos relacionados
Patrón irrefutable
Un patrón que siempre coincide.
Los patrones irrefutables son patrones que siempre coinciden. Los patrones irrefutables son los únicos patrones que pueden aparecer en contextos irrefutables: los contextos de patrones de declaración y asignación.
Documentos y recursos relacionados
Compilación Just-in-Time (JIT)
La compilación Just-in-Time (JIT) es un modo de compilación que compila el código en tiempo de ejecución para una iteración rápida del desarrollo.
La compilación Just-in-Time (JIT) convierte el código Dart en código de máquina nativo a medida que se ejecuta. Esto habilita funciones como hot reload y depuración enriquecida al permitir la compilación incremental. JIT se utiliza típicamente durante el desarrollo, mientras que las compilaciones de producción utilizan AOT.
Documentos y recursos relacionados
Late
Una palabra clave que permite la inicialización diferida de variables y que se utiliza típicamente con variables que no admiten valores nulos.
La palabra clave late en Dart se utiliza para indicar que una variable
se inicializará más tarde, después de su declaración, pero antes de ser utilizada.
Esto ayuda a evitar la necesidad de hacer que una variable admita valores nulos (?) cuando
sabes que definitivamente recibirá un valor, solo que no de inmediato.
El uso de late difiere la inicialización, lo que te permite escribir código más
flexible y legible, especialmente cuando manejas dependencias
o configuraciones complejas.
Por ejemplo:
late String description;
void setup() {
description = 'This will be initialized before use.';
}
Ten cuidado con las variables late que forman parte de una API pública.
Si un cliente accede a la variable antes de que se inicialice,
se encontrará con un LateInitializationError, el cual proporciona
poco contexto. En tales casos, considera usar una
variable privada que admita valores nulos con un getter público que lance un
error descriptivo (por ejemplo, StateError) si se accede demasiado pronto, ya que
esto puede ofrecer una retroalimentación más clara a los usuarios de la API, a pesar de la
complejidad adicional.
También puedes usar late final cuando la variable solo deba establecerse una vez.
Esto es útil en escenarios donde el valor no está disponible en el momento de la construcción del
objeto, como dependencias cíclicas en gráficos de objetos.
Ejemplo:
class LinkedQueue<T> {
late final QueueLink<T> _head;
LinkedQueue() {
_head = QueueLink<T>._head(owner: this); // Cyclic reference between objects
}
}
Ten precaución: si se accede a una variable late antes de inicializarla o si nunca se inicializa, causará un error en tiempo de ejecución.
Documentos y recursos relacionados
Librería
Una única unidad de compilación en Dart, compuesta por un archivo de Dart principal y sus partes.
Una librería de Dart es una única unidad de compilación en Dart,
compuesta por un archivo principal .dart y cualquier número opcional de partes.
Las librerías tienen su propio ámbito privado.
Documentos y recursos relacionados
Privado de la librería
Accesibilidad restringida a la librería donde está definida.
En Dart, la privacidad tiene como ámbito la librería en lugar de la clase.
Cualquier identificador que tenga como prefijo un guion bajo, como por ejemplo _myVariable, es privado de la librería.
Documentos y recursos relacionados
Lockfile
Un archivo llamado pubspec.lock que especifica las versiones de cada dependencia.
Un archivo llamado pubspec.lock que especifica las
versiones concretas y otra información de identificación para cada
dependencia directa y transitiva
de la cual depende un paquete.
A diferencia del pubspec, el cual solo enumera dependencias directas y permite rangos de versiones, el lockfile fija de manera integral todo el gráfico de dependencias a versiones específicas de los paquetes. Un lockfile garantiza que puedas recrear la configuración exacta de los paquetes utilizados por una aplicación.
El lockfile lo genera automáticamente pub cuando ejecutas
pub get, pub upgrade, o
pub downgrade.
pub incluye un hash de contenido para cada dependencia
para verificarlo durante futuras resoluciones.
Si tu paquete es un paquete de aplicación, normalmente registrarás esto en tu control de código fuente. Para paquetes normales (de librería), por lo general no lo harás.
Documentos y recursos relacionados
Aplicación de mixin
Una clase creada cuando se aplica un mixin a una clase.
Una aplicación de mixin es la clase creada cuando se aplica un mixin a una clase. Por ejemplo, considera las siguientes declaraciones:
class A {}
mixin M {}
class B extends A with M {}
La clase B es una subclase de la aplicación del mixin de M en A,
a veces nombrada como A+M. La clase A+M es una subclase de
A y
tiene miembros que se copian de M.
Puedes darle un nombre real a una aplicación de mixin definiéndola como:
class A {}
mixin M {}
class A_M = A with M;
Dada esta declaración de A_M, la siguiente declaración de B es
equivalente a la declaración de B en el ejemplo original:
class B extends A_M {}
Documentos y recursos relacionados
Null safety
Una característica del sistema de tipos de Dart donde las variables no pueden
contener null a menos que su tipo lo permita explícitamente.
Null safety en Dart es una característica del lenguaje que ayuda a
prevenir errores causados por el acceso no intencionado a valores de null.
Con Null Safety, los tipos no admiten valores nulos por defecto:
una variable no puede contener null a menos que
declares su tipo como nullable con un sufijo ?.
int count = 42; // Can't be null.
int? maybeCount; // Can be null (and is initialized to null by default).
El Null Safety de Dart es sound.
Si el sistema de tipos determina que una variable tiene un tipo no nullable,
se garantiza que esa variable nunca será null en tiempo de ejecución.
Esta garantía de solidez se hace cumplir a través de
una combinación de análisis estático y comprobaciones en tiempo de ejecución,
y permite optimizaciones del compilador
que producen un código más pequeño y eficiente.
Documentos y recursos relacionados
Obviamente tipado
Una expresión cuyo tipo se puede determinar mecánicamente sin ambigüedad.
Una expresión en Dart se considera obviamente tipada cuando su tipo queda inmediatamente claro a partir de su sintaxis, sin necesidad de inferencia o análisis complejos.
Este concepto se utiliza principalmente en reglas de linter como
omit_obvious_local_variable_types y specify_nonobvious_property_types
para decidir si una anotación de tipo explícita es redundante o necesaria.
Expresiones obviamente tipadas
#Los siguientes tipos de expresiones se consideran obviamente tipadas:
- Literales que no son de colección:
1,true,'Hello'. - Literales de colección con argumentos de tipo explícitos:
<int>[],<String, bool>{}. - Literales homogéneos de lista/conjunto cuyos elementos están obviamente tipados:
[1, 2, 3],{true, false}. - Literales homogéneos de mapa con tipos obvios de clave y valor:
{1: 10, 2: 20}. - Literales de record con tipos obvios:
(1, enabled: true). - Variables locales y parámetros formales que no han sido promocionados.
- Creación de instancias con clases no genéricas o argumentos de tipo explícitos:
C(),Set<int>(). - Cascadas con objetivos obviamente tipados:
StringBuffer('Hello, ')..write('world!'). - Conversiones de tipo (type casts):
x as int. - Expresiones condicionales con ramas obviamente tipadas del mismo tipo:
condition ? 1 : 2. - Pruebas de tipo (type tests):
x is String. - Expresiones throw:
throw Exception(). - Expresiones entre paréntesis cuyo contenido está obviamente tipado:
('Hello!'). thisy literales de tipo:String.
En esta lista, homogéneo significa "donde cada elemento tiene el mismo tipo".
Por ejemplo, {1: 'one', 2: 'two'} es un mapa homogéneo,
but {1: 'one', 1.5: true} no lo es.
Por el contrario, si el tipo de una expresión requiere inferencia (por ejemplo
[1, 2.5] → List<num>), no
se considera obviamente tipado.
Esta distinción ayuda a las reglas de estilo a decidir cuándo exigir u omitir anotaciones de tipo para mayor claridad y consistencia.
Documentos y recursos relacionados
Inferencia de sobreescritura
Cómo se infieren los tipos omitidos en la declaración de un método.
La inferencia de sobreescritura (override inference) es el proceso mediante el cual se infiere cualquier tipo faltante en la declaración de un método basándose en los tipos correspondientes del método o métodos que este sobreescribe.
Si un método candidato (el método al que le falta información de tipo) sobreescribe un único método heredado, entonces se infieren los tipos correspondientes del método sobreescrito. Por ejemplo, considera el siguiente código:
class A {
int m(String s) => 0;
}
class B extends A {
@override
m(s) => 1;
}
La declaración de m en B es una candidata porque
le faltan tanto el tipo de retorno como el tipo de parámetro.
Debido a que sobreescribe un único método (el método m en A),
se utilizarán los tipos del método sobreescrito para inferir
los tipos faltantes y será como si el método en B se hubiera
declarado como int m(String s) => 1;.
Si un método candidato sobreescribe múltiples métodos, y el tipo de función de uno de esos métodos sobreescritos, Ms, es un supertipo de los tipos de función de todos los demás métodos sobreescritos, entonces Ms se utiliza para inferir los tipos faltantes. Por ejemplo, considera el siguiente código:
class A {
int m(num n) => 0;
}
class B {
num m(int i) => 0;
}
class C implements A, B {
@override
m(n) => 1;
}
La declaración de m en C es candidata para la inferencia de sobreescritura porque
le faltan tanto el tipo de retorno como el tipo de parámetro.
Sobreescribe tanto a m en A como a m en B, por lo que el compilador debe
elegir uno de ellos a partir del cual se puedan inferir los tipos faltantes.
Pero debido a que el tipo de función de m en A (int Function(num)) es
un supertipo del tipo de función de m en B (num Function(int)),
se utiliza la función en A para inferir los tipos faltantes. El resultado es
el mismo que declarar el método en C como int m(num n) => 1;.
Es un error si ninguno de los métodos sobreescritos tiene un tipo de función que sea un supertipo de todos los demás métodos sobreescritos.
Documentos y recursos relacionados
Paquete
Un directorio con una colección de librerías de Dart, recursos, y un archivo pubspec.yaml que los describe.
Un paquete de Dart es una colección de librerías y recursos de Dart en
un directorio, con un archivo pubspec.yaml
en la raíz de ese directorio.
Los paquetes pueden tener dependencias de otros paquetes
y pueden ser dependencias en sí mismos.
El directorio /lib de un paquete contiene las librerías públicas
que
otros paquetes pueden importar y usar.
También pueden incluir scripts para ejecutarse directamente.
Un paquete que no está destinado a que otros paquetes dependan de él
es un paquete de aplicación.
Los paquetes compartidos se publican en pub.dev,
pero también puedes tener paquetes no publicados.
No registres el lockfile de un paquete en el control de código fuente, ya que las librerías deben admitir un rango de versiones de dependencia. Las restricciones de versión de las dependencias directas de un paquete deben ser lo más amplias posible, garantizando al mismo tiempo que las dependencias sean compatibles con las versiones con las que se realizaron las pruebas.
Dado que el versionamiento semántico requiere que
las librerías incrementen sus números de versión principal para
cualquier cambio incompatible con versiones anteriores, los paquetes normalmente
requerirán que las versiones de sus dependencias sean mayores o iguales a
las versiones probadas y menores que la siguiente versión principal.
Así que si tu librería dependiera del paquete (ficticio) transmogrify y
la probaras en la versión 1.2.1, tu restricción de versión sería
^1.2.1.
Documentos y recursos relacionados
Uploader del paquete
Un usuario de pub.dev que tiene permisos administrativos para un paquete.
Un uploader del paquete es alguien que tiene permisos administrativos para un paquete. Un uploader del paquete puede subir nuevas versiones del paquete, y también puede agregar y eliminar a otros uploaders para ese paquete.
Si un paquete tiene un publicador verificado, entonces todos los miembros del publicador pueden subir el paquete.
Documentos y recursos relacionados
Archivo part
Un archivo fuente de Dart que contiene una directiva part of.
Un archivo part es un archivo fuente de Dart que contiene una directiva part of
y se incluye en una librería utilizando la directiva part.
Documentos y recursos relacionados
Potentially constant
An expression that can be evaluated at compile time if all constructor parameters of the enclosing constant constructor are replaced by constant values, and all type parameters are replaced by concrete types.
A potentially constant expression is an expression that is valid in contexts where a constant is required, such as initializer lists of constant constructors, even though it might not be a compile-time constant itself because it references constructor parameters or type parameters.
If all constructor parameters of the enclosing constant constructor are replaced by constant values of the appropriate type, and all type parameters are replaced by concrete types, a potentially constant expression becomes a compile-time constant expression.
For example, in the following class:
class ConstPoint {
final int x;
final int y;
const ConstPoint(int a) : x = a, y = a + 1;
}
The initializing expressions a and a + 1
are potentially constant expressions.
They are not constant expressions
because the value of a depends on how the constructor is called.
However, if the constructor is invoked with const ConstPoint(3),
the parameter a is replaced by the constant 3,
making 3 and 3 + 1 constant expressions.
Documentos y recursos relacionados
Potencialmente no nulo
Un tipo que no admite valores nulos explícitamente o debido a que es un parámetro de tipo.
Un tipo es potencialmente no nulo si no admite valores nulos explícitamente o si es un parámetro de tipo.
Un tipo no admite valores nulos explícitamente si es un
nombre de tipo que no va seguido de un signo de interrogación (?).
Ten en cuenta que existen algunos tipos que siempre admiten valores nulos, como
Null y dynamic, y que FutureOr
solo es no nullable si
no va seguido de un signo de interrogación y
el argumento de tipo no admite valores nulos (como por ejemplo FutureOr<String>).
Los parámetros de tipo son potencialmente no nulos porque el tipo real
en tiempo de ejecución (el tipo especificado como argumento de tipo) podría no admitir valores nulos.
Por ejemplo, dada la declaración de class C<T> {},
el tipo C podría usarse con un argumento de tipo no nullable como en
C<int>.
Documentos y recursos relacionados
Hash de contenido de pub
Hashes SHA256 mantenidos por pub.dev para validar la integridad del paquete.
El repositorio pub.dev mantiene un hash de contenido SHA256 de cada versión de cada paquete que aloja. Los clientes de pub utilizan este hash para validar la integridad de los paquetes descargados y protegerse contra cambios en el repositorio de origen.
Cuando dart pub get descarga un paquete,
calcula el hash del archivo descargado.
El hash de cada dependencia alojada se almacena con la
resolución en el lockfile.
El cliente de pub utiliza este hash de contenido para verificar que
al ejecutar dart pub get nuevamente utilizando el mismo lockfile,
posiblemente en una computadora diferente, se utilicen exactamente los mismos paquetes.
Si el hash bloqueado no coincide con lo que está actualmente en la caché de pub, pub vuelve a descargar el archivo. Si aún no coincide, el lockfile se actualiza y se imprime una advertencia.
Para hacer que una discrepancia se convierta en un error en lugar de una advertencia,
usa la opción --enforce-lockfile
para dart pub get.
Con esta opción, si pub no puede encontrar archivos de paquetes con los mismos hashes,
la resolución de dependencias falla y el lockfile no se modifica.
Documentos y recursos relacionados
Caché del sistema de pub
Un directorio donde pub almacena los paquetes remotos descargados.
Cuando pub obtiene un paquete remoto,
lo descarga en un único directorio de caché del sistema de pub.
En macOS y Linux, este directorio tiene el valor predeterminado de ~/.pub-cache.
En Windows, el directorio tiene el valor predeterminado de %LOCALAPPDATA%\Pub\Cache,
aunque su ubicación exacta puede variar según la versión de Windows.
Puedes especificar una ubicación diferente utilizando la
variable de entorno PUB_CACHE.
Una vez que los paquetes están en la caché del sistema,
pub crea un archivo package_config.json que asigna cada paquete
utilizado por tu aplicación al paquete correspondiente en la caché.
Solo tienes que descargar una versión dada de un paquete una vez
y luego puedes reutilizarla en tantos paquetes como desees.
Si especificas la bandera --offline para usar paquetes almacenados en caché,
puedes eliminar y regenerar tus archivos package_config.json
sin tener que acceder a la red.
Documentos y recursos relacionados
Espacio de trabajo de pub
Una colección de paquetes que se desarrollan juntos con una resolución compartida de sus restricciones de dependencia.
Un espacio de trabajo de pub asocia una colección de paquetes locales que se tratan como una única unidad durante el desarrollo, lo que permite una resolución compartida de sus restricciones de dependencia. Es útil para el desarrollo en un monorepo.
Los paquetes comparten los archivos pubspec.lock y
.dart_tool/package_config.json en el directorio raíz del espacio de trabajo.
Documentos y recursos relacionados
Librería pública
Una librería que se encuentra en el directorio lib de un paquete, pero
no dentro del directorio lib/src.
Una librería pública es una librería que se encuentra dentro del directorio lib
del paquete, pero no dentro del directorio lib/src.
Documentos y recursos relacionados
Corrección rápida
Una edición de código local y automatizada orientada a solucionar el problema informado por un diagnóstico específico.
Documentos y recursos relacionados
Refactorización
Una edición de código orientada a modificaciones que no son locales o que requieren la interacción del usuario.
Una refactorización (refactor) es una edición de código orientada a modificaciones que no son locales o que requieren la interacción del usuario. Algunos ejemplos de refactorizaciones incluyen renombrar, eliminar o extraer código.
Documentos y recursos relacionados
Patrón refutable
Un patrón que se puede probar con un valor.
Un patrón refutable es un patrón que se puede probar con un valor para determinar si el patrón coincide con el valor. Si no coincide, el patrón refuta, o niega, la coincidencia. Los patrones refutables aparecen en contextos de coincidencia.
Documentos y recursos relacionados
Genéricos reificados
La preservación de los argumentos de tipos genéricos en tiempo de ejecución,
lo que permite realizar comprobaciones de tipo contra el tipo genérico completo,
como por ejemplo con is List<String>.
En Dart, los argumentos de tipos genéricos están reificados, lo que significa que mantienen un seguimiento de sus argumentos de tipo en tiempo de ejecución. Esto te permite inspeccionar y comprobar el tipo genérico completo de un objeto, no solo su tipo sin procesar sin argumentos de tipo.
Por ejemplo, puedes comprobar si una lista es
específicamente un subtipo de List<String>:
final names = ['Dart', 'Flutter', 'Jaspr'];
print(names is List<String>); // Prints: true
print(names is List<int>); // Prints: false
Los genéricos reificados también permiten las comprobaciones de tipos en tiempo de ejecución de Dart
para tipos genéricos covariantes.
Para garantizar la solidez (soundness), al agregar un elemento a una lista,
Dart valida en tiempo de ejecución que el elemento coincida con
el argumento de tipo real de la lista.
Por ejemplo, si intentas agregar un double a una List<int>,
debido a que double no es un subtipo de int,
Dart lanza un error en tiempo de ejecución:
List<num> numbers = <int>[1, 2, 3]; // Explicitly typed as `List<num>`.
numbers.add(1.5); // Throws a TypeError because `1.5` isn't an int.
Esto contrasta con algunos otros lenguajes de programación, como Java,
donde los argumentos de tipo se eliminan antes de la ejecución mediante la borradura de tipos.
Por lo tanto, en esos lenguajes, aunque puedes probar si un objeto es una List,
no puedes probar si es una List<String>.
Documentos y recursos relacionados
Ámbito
La región de un programa donde un nombre (como una variable, función o clase) es visible y puede ser referenciado.
Scope define dónde los identificadores (como variables, parámetros, funciones, y clases) son accesibles dentro de un programa.
Dart utiliza el ámbito léxico (estático), lo que significa que el ámbito de un nombre se determina por la estructura del código fuente, no por cómo se ejecuta el programa en tiempo de ejecución. Consulta ámbito léxico para obtener más detalles.
Los tipos comunes de ámbito en Dart incluyen:
- Ámbito de clase: Miembros accesibles dentro de una clase.
- Ámbito de bloque: Variables declaradas dentro de bloques como
if,for, owhile. - Ámbito de función: Parámetros y variables locales dentro de una función.
- Ámbito de librería: Declaraciones de nivel superior visibles dentro de una librería.
Documentos y recursos relacionados
Shadowing
Cuando una declaración local oculta a otra con el mismo nombre.
El shadowing ocurre cuando una declaración local, como una variable o un parámetro, utiliza el mismo nombre que una declaración existente en un ámbito externo, haciendo que la externa sea inaccesible dentro del ámbito interno.
Aunque es válido en Dart, el shadowing puede provocar un código confuso o un comportamiento no deseado. Por lo tanto, generalmente se desaconseja a menos que se use deliberadamente para mejorar la claridad de tu código.
Ejemplo
#En este ejemplo, la variable local message dentro de
la función printMessage hace shadowing a la variable de nivel superior
message:
final message = 'Global';
void printMessage() {
final message = 'Local'; // Shadows the global `message` variable.
print(message); // Prints: Local
}
void main() {
printMessage();
print(message); // Prints: Global
}
El shadowing también puede ocurrir en bloques anidados:
void main() {
final value = 10;
if (true) {
final value = 20; // Shadows the outer `value` variable.
print(value); // Prints: 20
}
print(value); // Prints: 10
}
Sound
Una garantía del sistema de tipos de que el valor en tiempo de ejecución de una expresión siempre coincide con su tipo estático.
Un sistema de tipos es sound cuando garantiza que un programa
nunca pueda entrar en un estado en el que una expresión se evalúe como un valor
que no coincida con el tipo estático de la expresión.
Por ejemplo, si el tipo estático de una expresión es String,
se garantiza que el valor en tiempo de ejecución es un String cuando se evalúa.
El sistema de tipos de Dart es sound. Hace cumplir esta garantía a través de una combinación de comprobación estática (errores en tiempo de compilación) y comprobaciones en tiempo de ejecución:
- Las comprobaciones estáticas detectan la mayoría de los errores de tipo en tiempo de compilación,
como asignar un
Stringa una variable con un tipoint. - Las comprobaciones en tiempo de ejecución manejan casos que el compilador no puede verificar estáticamente,
como las conversiones con
asy los argumentos de tipo genérico covariante.
La solidez (soundness) te ofrece varios beneficios prácticos:
- Los errores relacionados con los tipos salen a la luz en tiempo de compilación. Un sistema de tipos sound obliga a que el código sea inequívoco sobre sus tipos, por lo que los errores relacionados con los tipos que podrían ser difíciles de encontrar en tiempo de ejecución se detectan de forma temprana.
- Código más legible. Puedes confiar en que un valor realmente tiene su tipo declarado. En el Dart sound, los tipos no pueden mentir.
- Código más fácil de mantener. Cuando cambias una parte del código, el sistema de tipos sound permite que las herramientas de Dart te adviertan sobre otras partes del código que se rompieron.
- Mejor compilación ahead-of-time (AOT). Los tipos sólidos ayudan al compilador a generar código nativo más pequeño y eficiente.
La solidez (soundness) no significa que Dart detecte cada error. Aún puedes escribir código con errores de lógica, errores por diferencia de uno, y otros problemas que el sistema de tipos no aborda. Simplemente significa que los tipos mismos son siempre confiables.
Documentos y recursos relacionados
Subclase
Una clase que hereda la implementación de otra clase.
Una subclase es una clase que hereda la implementación de otra clase utilizando
la palabra clave extends, o mediante la
aplicación de mixin.
// A is a subclass of B; B is the superclass of A.
class A extends B {}
// B1 has the superclass `A with M`, which has the superclass A.
class B1 extends A with M {}
Una relación de subclase también implica una relación de subtipo asociada.
Por ejemplo, class A define implícitamente un tipo asociado A
en el cual habitan las instancias de la clase A.
Por lo tanto, class A extends B no solo declara que la clase
A es una subclase de B, sino que también establece que el
tipo A es un
subtipo del tipo B.
Las relaciones de subclase son un subconjunto de las relaciones de subtipo.
Cuando la documentación dice "S debe ser un subtipo de T",
está bien que S sea una subclase de T.
Sin embargo, lo contrario no es cierto: no todos los subtipos son subclases.
Documentos y recursos relacionados
Subtipo
Un tipo que se puede usar en cualquier lugar donde se espere un valor de su supertipo.
Una relación de subtipo es aquella en la que un valor de cierto tipo es sustituible
donde se espera el valor de otro tipo, el supertipo.
Por ejemplo, si S es un subtipo de T,
entonces puedes sustituir un valor de tipo S
donde se espera un valor de tipo T.
Un subtipo admite todas las operaciones de su supertipo (y posiblemente algunas operaciones adicionales). En la práctica, esto significa que puedes asignar el valor de un subtipo a cualquier ubicación que espere el supertipo, y todos los métodos del supertipo estarán disponibles en el subtipo.
Esto es cierto al menos estáticamente. Una API específica podría no permitir la sustitución en tiempo de ejecución, dependiendo de sus operaciones.
Algunas relaciones de subtipo se basan en la estructura del tipo,
como ocurre con los tipos que admiten valores nulos (por ejemplo, int es un subtipo de int?)
y los tipos de función
(por ejemplo, String Function() es un subtipo de void Function()).
Los subtipos también se pueden introducir para las clases mediante la implementación o la herencia (directa o indirecta):
// A is a subtype of B, but NOT a subclass of B.
class A implements B {}
// C is a subtype AND a subclass of D.
class C extends D {}
Documentos y recursos relacionados
Super parámetro
Un parámetro de constructor que pasa automáticamente un argumento al constructor de la superclase.
Un super parámetro es un atajo de sintaxis que simplifica el paso de parámetros desde el constructor de una subclase hacia el constructor de una superclase.
En lugar de declarar manualmente el parámetro
y pasarlo en la lista de inicialización,
puedes anteponer el prefijo super. al nombre del parámetro.
Por ejemplo, en Hammer({required super.price}),
el valor price se pasa
directamente al constructor de la superclase.
Documentos y recursos relacionados
Tipo superior
Un tipo que es un supertipo de todos los demás tipos,
incluyendo Object?.
Un tipo superior es cualquier tipo que sea un supertipo de Object?.
Debido a que Object? es en sí mismo un supertipo de todos los demás tipos de Dart,
un tipo superior se sitúa en la cima de la jerarquía de tipos,
por lo que puedes asignar cualquier valor a una variable con un tipo superior.
Dart tiene muchos tipos superiores, pero los principales son los tres siguientes:
-
Object?- El supertipo nullable de todos los tipos. Cada valor en Dart, incluyendonull, es unObject?. -
dynamic- Un tipo especial que deshabilita la comprobación estática de tipos. Al igual queObject?, acepta cualquier valor, pero también te permite acceder a cualquier miembro en él sin un error en tiempo de compilación. En su lugar, la comprobación se difiere al tiempo de ejecución. -
void- Un tipo que indica que su valor no está destinado a ser utilizado. Puedes asignar cualquier valor avoid, pero no puedes usar el resultado sin una conversión.
Además de estos tres principales, existen otros tipos superiores compuestos.
FutureOr<T> es un tipo superior siempre que T sea un tipo superior,
y agregar ? a ciertos tipos también produce un tipo superior.
Por ejemplo, FutureOr<Object?>, FutureOr<Object>?,
y dynamic? son todos tipos superiores.
A diferencia de Object?, el tipo no nullable Object es
el supertipo de todos los tipos no nullables, pero no es un verdadero tipo superior
porque no es un supertipo de Null.
El tipo superior es el inverso del tipo inferior.
Mientras que un tipo superior es un supertipo de todo,
el tipo inferior (Never en Dart) es un subtipo de todo.
Object? a = 42; // OK: int is a subtype of Object?.
Object? b = 'hello'; // OK: String is a subtype of Object?.
Object? c = null; // OK: Null is a subtype of Object?.
dynamic d = [1, 2, 3]; // OK: List<int> is a subtype of dynamic.
void e = true; // OK: bool is a subtype of void.
Dependencia transitiva
Una dependencia que un paquete utiliza indirectamente porque una de sus dependencias la requiere.
Una dependencia transitiva es una dependencia que un paquete utiliza indirectamente porque una de sus dependencias, o las dependencias de estas, la requiere.
Si tu paquete depende de A, que a su vez depende de B que depende de C, entonces A es una dependencia directa y B y C son transitivas.
Documentos y recursos relacionados
Tree shaking
Una optimización del compilador que elimina el código no utilizado.
Tree shaking es una optimización del compilador que analiza qué partes del código son realmente utilizadas por una aplicación y elimina todo lo demás del resultado final.
Esto hace que el programa compilado sea más pequeño y rápido de cargar, ya que el código muerto que nunca se referencia no termina en el binario o paquete.
El tree shaking es especialmente importante para las aplicaciones web y móviles, donde el tamaño del código afecta directamente el tiempo de descarga y el rendimiento en tiempo de ejecución.
Por ejemplo, si importas una librería que define diez funciones de nivel superior, pero tu aplicación solo llama a dos, el tree shaking garantiza que las otras ocho funciones se excluyan del resultado compilado.
Documentos y recursos relacionados
Alias de tipo
Un nombre definido por el usuario para un tipo existente.
Un alias de tipo es un nombre alternativo que hace referencia a otro tipo.
Se pueden usar para simplificar definiciones de tipos complejas, mejorar la legibilidad o crear un significado semántico en el código.
Dart admite la definición de alias de tipo mediante la palabra clave typedef.
Puedes crear alias de funciones, clases e incluso tipos genéricos.
Ejemplos
#Alias de tipo de función
#typedef StringTransformer = String Function(String);
void printTransformed(String input, StringTransformer transformer) {
print(transformer(input));
}
void main() {
printTransformed('hello', (str) => str.toUpperCase()); // Output: HELLO
}
Alias de clase
#class HttpClient {}
typedef Client = HttpClient;
Client client = HttpClient();
Los alias de tipo no crean nuevos tipos, solo proporcionan nombres alternativos.
Documentos y recursos relacionados
No inicializado
A una variable que ha sido declarada pero a la que aún no se le ha asignado un valor.
Una variable está no inicializada cuando ha sido declarada pero aún no se le ha asignado un valor.
Las variables que no admiten valores nulos deben inicializarse antes de ser leídas, ya sea en el punto de declaración o a través de todas las rutas de ejecución posibles antes del primer uso:
void f() {
int x;
print(x); // Error: variable 'x' must be assigned before it can be used.
}
Las variables que admiten valores nulos y que no se inicializan explícitamente tienen el valor predeterminado de null:
int? count; // Implicitly initialized to null.
print(count); // Prints: null
Para diferir la inicialización de una variable que no admite valores nulos,
usa la palabra clave late. Una variable late está no inicializada
hasta su primera asignación, y acceder a ella antes de que se asigne
lanza un error de inicialización diferida en tiempo de ejecución:
late String name;
print(name); // Throws a late initialization error if not yet assigned.
Documentos y recursos relacionados
Varianza y posiciones de varianza
Cómo el cambio de un argumento de tipo de un tipo afecta la relación entre el tipo original y el resultante.
En Dart, cambiar el argumento de tipo de una declaración de tipo (como una clase) o el tipo de retorno de una función, cambia la relación de tipo general en la misma dirección (covariante).
Sin embargo, cambiar el tipo de los tipos de parámetros de una función cambia la relación de tipo general en la dirección opuesta (contravariante).
Se dice que un parámetro de tipo de una clase (u otra declaración de tipo, como un mixin) es covariante cuando el tipo en su conjunto "co-varía" con el argumento de tipo real. En otras palabras, si el argumento de tipo se reemplaza por un subtipo, entonces el tipo en su conjunto también es un subtipo.
Por ejemplo, el parámetro de tipo de la clase List es covariante porque
los tipos de lista co-varían con su argumento de tipo: List<int> es un subtipo de
List<Object> porque int es un subtipo de Object.
En Dart, todos los parámetros de tipo de todas las declaraciones de class, mixin, mixin class y enum son covariantes.
Sin embargo, los tipos de función son diferentes:
Un tipo de función es covariante en su tipo de retorno, pero
el opuesto (conocido como contravariante) en sus tipos de parámetros.
Por ejemplo, el tipo int Function(int) es un
subtipo del tipo Object Function(int), pero es un
supertipo de int Function(Object).
Esto tiene sentido si consideras su sustituibilidad.
Si llamas a una función con un tipo estático de int Function(int),
esa función en realidad puede ser de tipo int Function(Object) en tiempo de ejecución.
Según el tipo estático, esperas poder pasarle un int.
Eso estará bien ya que la función en realidad acepta cualquier Object,
y esto incluye cada objeto de tipo int.
De manera similar, el resultado devuelto será de tipo int,
lo cual también es lo que esperas basándote en el tipo estático.
Por lo tanto, int Function(Object) es un subtipo de int Function(int).
Ten en cuenta que todo se invierte para los tipos de parámetros.
En particular, esta relación de subtipo entre los tipos de funciones requiere que
exista la relación de subtipo opuesta para el tipo de parámetro.
Por ejemplo, void Function(Object) es un subtipo de void Function(int)
porque int es un subtipo de Object.
Con un tipo más complejo como List<void Function(int)>,
debes considerar las posiciones en el tipo.
Para lograr esto, convierte una de las partes del tipo en un marcador de posición,
y luego considera qué sucede con el tipo cuando
se colocan diferentes tipos en esa posición.
Por ejemplo, considera List<void Function(_)> como una plantilla para
un tipo donde puedes poner diferentes tipos en lugar del marcador de posición _.
Este tipo es contravariante en la posición donde ocurre ese marcador de posición.
Lo siguiente ilustra esto al sustituir Object e int por _.
List<void Function(Object)> es un subtipo de List<void Function(int)>
porque void Function(Object) es un subtipo de void Function(int)
porque
void es un subtipo de void (los tipos de retorno) e
int es un subtipo de Object (los tipos de parámetros, en el orden opuesto).
Por lo tanto, el tipo en _ varía en la dirección opuesta al
tipo List<void Function(_)> en su conjunto, y esta
'dirección opuesta' por definición lo convierte en una posición contravariante.
Una posición covariante se define de manera similar.
Por ejemplo, _ está en una posición covariante en el tipo List<_>,
y _ también está en una posición covariante en el tipo _ Function(int).
Existe otro tipo de posición conocida como invariante, pero ocurre con mucha menos frecuencia, por lo que los detalles se omiten aquí.
En la práctica, a menudo es suficiente saber que los argumentos de tipo de una clase, mixin, etc. están en una posición covariante, al igual que el tipo de retorno de un tipo de función, pero los tipos de parámetros están en una posición contravariante.
Documentos y recursos relacionados
Publicador verificado
Un publicador de paquetes en el sitio pub.dev cuya identidad ha sido verificada por pub.dev.
Un publicador verificado es una colección de uno o más usuarios que están identificados con un nombre de dominio único. La propiedad del nombre de dominio es verificada por pub.dev, como por ejemplo para el publicador dart.dev en pub.dev por el equipo de Dart.
Documentos y recursos relacionados
Restricción de versión
Una restricción asociada a cada dependencia que especifica con qué versiones se espera que funcione un paquete.
Una restricción de versión es un rango especificado de versiones compatibles
de una dependencia para un paquete.
Esto puede ser una sola versión (0.3.0) o un rango de versiones (^1.2.1).
Aunque también se permite any, por razones de rendimiento no se recomienda.
Los paquetes de librería siempre deben especificar restricciones de versión para cada una de sus dependencias que no sean de desarrollo. Los paquetes de aplicación, por otro lado, pueden permitir cualquier versión de sus dependencias, ya que utilizan el lockfile para gestionar las versiones de sus dependencias.
Documentos y recursos relacionados
Comodín (Wildcard)
Un símbolo (_) utilizado en lugar del nombre de una variable para
indicar un valor no utilizado en patrones y otros contextos.
Un comodín es el carácter de guion bajo (_) utilizado para
ignorar valores o indicar que un valor no se utiliza intencionadamente.
Se usa a menudo en patrones, desestructuración y expresiones switch para
coincidir con cualquier valor sin vincularlo a un nombre.
Los comodines ayudan a hacer el código más intencional al marcar claramente los valores que no se necesitan en un contexto específico.
Ejemplo:
// Ignoring the value in a for-each loop.
var names = ['Alice', 'Bob', 'Charlie'];
for (var _ in names) {
print('Someone is here!');
}
El patrón comodín (wildcard) es particularmente útil cuando:
- Solo necesitas ciertas partes de un valor desestructurado.
- Quieres mostrar explícitamente que se están ignorando algunos valores.
- Necesitas un caso de captura total en la coincidencia de patrones.
Documentos y recursos relacionados
Zona
Un mecanismo para personalizar el comportamiento del código asíncrono sin modificar el propio código asíncrono.
Una zona es un contexto de ejecución que te permite ejecutar código con un comportamiento personalizado para eventos asíncronos, como temporizadores, microtareas y errores no capturados.
Las zonas son útiles para:
- Registro (logging)
- Seguimiento de errores
- Mantener el estado específico de una solicitud a través de brechas asíncronas (por ejemplo, en aplicaciones de servidor)
- Probar y depurar el comportamiento asíncrono
Las zonas proporcionan una forma de rastrear e influir en la ejecución asíncrona sin necesidad de que el código asíncrono sea consciente de ello.
Puedes crear una nueva zona utilizando runZoned (o runZonedGuarded)
y sobreescribir el comportamiento específico de la zona, como el manejo de errores y los temporizadores.
Incluso print se puede sobreescribir, aunque no es asíncrono y solo
se incluye por conveniencia.
Ejemplo:
import 'dart:async';
void main() {
runZonedGuarded(() {
Future.delayed(Duration(seconds: 1), () {
throw 'Zone caught this error!';
});
}, (error, stackTrace) {
print('Caught error: $error');
});
}
En el ejemplo anterior, el error no capturado dentro del callback asíncrono es interceptado por la zona personalizada.
Documentos y recursos relacionados
A menos que se indique lo contrario, la documentación de este sitio refleja Dart 3.12.2.Informar de un problema.