Sistema de tipos sólido do Dart (Sound Type System)
Definição principal
O Dart é uma linguagem de tipagem estática sólida, protegida por um mecanismo duplo: verificação estática em tempo de compilação e validação em tempo de execução. Em execução, o valor armazenado em uma variável sempre corresponde ao tipo declarado estaticamente.
As regras de tipo são aplicadas de forma rigorosa, mas as anotações de tipo são opcionais; o compilador infere os tipos automaticamente.
A análise estática durante a compilação intercepta a maioria dos erros de tipo antecipadamente.
O que é Solidez (Soundness)
O código nunca entra em um estado de tipo inválido. Se uma expressão tem o tipo estático String, ela só retorna valores de texto em tempo de execução, sem aceitar outros tipos. Linguagens similares: Java, C#.
- Bloqueio na compilação:
int a = "abc";dispara erro imediato; - Bloqueio em tempo de execução: O cast com
aslança exceção se houver incompatibilidade de tipos.
Quatro vantagens principais
Erros de tipo aparecem na compilação, evitando falhas em produção difíceis de reproduzir causadas por conflitos de tipos;
Maior legibilidade do código, os tipos não enganam o desenvolvedor sobre o formato dos dados;
Código mais fácil de manter: ao alterar um trecho, as verificações de tipo marcam todas as partes impactadas;
Melhor desempenho na compilação AOT, as informações de tipo permitem ao compilador gerar código de máquina otimizado.
Exemplos
Erro estático causado por coleções dinâmicas
// Espera receber List<int>
void printInts(List<int> a) => print(a);
void main() {
// Sem declaração genérica, inferido como List<dynamic>
final list = [];
list.add(1);
list.add('2'); // Lista dinâmica aceita tipos misturados
printInts(list);
// Erro de compilação: List<dynamic> não pode ser atribuído a List<int>
}Code language: Dart (dart)
Declaração explícita de restrições genéricas
void printInts(List<int> a) => print(a);
void main() {
final list = <int>[]; // Especifica o genérico int de forma explícita
list.add(1);
// list.add('2'); // Erro de compilação, não é possível inserir string
list.add(2);
printInts(list); // Passa na validação estática normalmente
}Code language: Dart (dart)
Três regras centrais
1 Ao sobrescrever métodos, o tipo de retorno deve ser o tipo original ou um de seus subtipos (Regra do Produtor)
Os valores retornados por métodos da classe pai são produtores. As subclasses podem retornar subtipos mais específicos, mas não tipos sem relação.
Exemplo
Caso válido
class Animal {
Animal get parent => Animal();
}
class HoneyBadger extends Animal {
@override
HoneyBadger get parent => HoneyBadger(); // Subtipo, válido
}Code language: Dart (dart)
Caso inválido
class Root {}
class HoneyBadger extends Animal {
@override
Root get parent => Root(); // Tipo sem relação, erro de compilação
}Code language: Dart (dart)
2 Ao sobrescrever métodos, o tipo dos parâmetros deve ser o tipo original ou um de seus supertipos
Parâmetros de método são consumidores. Subclasses só podem ampliar o escopo do parâmetro (usar supertipo); restringir para um subtipo quebra a segurança de tipos.
Exemplo válido (parâmetro ampliado para Object)
class Animal {
void chase(Animal a) {}
}
class HoneyBadger extends Animal {
@override
void chase(Object a) {} // Object é supertipo de Animal, válido
}Code language: Dart (dart)
Exemplo inválido (parâmetro restrito à subclasse Mouse)
class Mouse extends Animal {}
class Cat extends Animal {
@override
void chase(Mouse a) {} // Restrição do tipo do parâmetro, erro estático
}
void main() {
Animal cat = Cat();
cat.chase(Alligator()); // Teoricamente aceita Alligator, causando insegurança de tipos
}Code language: Dart (dart)
Regra 3: Não é possível atribuir diretamente uma coleção genérica dinâmica a uma coleção genérica de tipo definido
List<dynamic> e List<Dog> não podem ser atribuídos diretamente a List<Cat> e geram erro de compilação. Não existe conversão implícita entre coleções genéricas diferentes.
class Cat {}
class Dog {}
void main() {
List<Cat> foo = <dynamic>[Dog()]; // Erro estático de compilação
List<dynamic> bar = <dynamic>[Dog(), Cat()]; // Válido, lista dinâmica aceita tipos misturados
}Code language: Dart (dart)
Validação de tipos em tempo de execução
As verificações estáticas não cobrem todos os cenários; validações extras rodam em tempo de execução e lançam exceções quando os tipos não correspondem.
Erro em tempo de execução por cast genérico
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
void main() {
List<Animal> animals = <Dog>[Dog()];
List<Cat> cats = animals as List<Cat>;
// Exceção em tempo de execução: não é possível converter List<Dog> para List<Cat>
}Code language: Dart (dart)
Downcast implícito de dynamic (verificado em tempo de execução)
Atribuir uma variável dynamic a um tipo concreto dispara conversão implícita validada na execução; lança erro se houver incompatibilidade.
int assumeString(dynamic object) {
String string = object; // Verifica em tempo de execução se object é String
return string.length;
}
void main() {
assumeString("test"); // Executa normalmente
assumeString(123); // Erro em tempo de execução: int não pode ser convertido para String
}Code language: Dart (dart)
Desabilitar casts implícitos (modo de tipos rigoroso)
Habilite o modo de conversões rigorosas no arquivo analysis_options.yaml para bloquear downcasts implícitos vindos de dynamic
analyzer:
language:
strict-casts: trueCode language: JavaScript (javascript)
Inferência de Tipos (Type Inference)
O Dart infere automaticamente os tipos de variáveis, genéricos e valores retornados de métodos. Quando faltam informações de tipo, o padrão inferido é dynamic.
Inferência de variáveis comuns
// Inferido automaticamente como Map<String, Object>
var arguments = {'argA': 'hello', 'argB': 42};
var x = 3; // x inferido como int
x = 4.0; // Erro, int não armazena double
num y = 3; // Declarado explicitamente como num, aceita int e double
y = 4.0; // VálidoCode language: Dart (dart)
Inferência de construtores genéricos / coleções
List<int> listOfInt = []; // Inferido automaticamente como <int>[]
var listOfDouble = [3.0]; // Inferido List<double>
var ints = listOfDouble.map((x) => x.toInt());
// x inferido double, callback retorna int → ints é Iterable<int>Code language: Dart (dart)
Sistema de tipos sólido