Sistema de tipos sólido

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 as lanç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

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *