Records
Resumo da estrutura de dados record em Dart.
Records são um tipo agregado anônimo e imutável. Como outros tipos de coleção, eles permitem agrupar múltiplos objetos em um único objeto. Ao contrário de outros tipos de coleção, records têm tamanho fixo, são heterogêneos e tipados.
Records são valores reais; você pode armazená-los em variáveis, aninhá-los, passá-los de e para funções, e armazená-los em estruturas de dados como listas, maps e sets.
Sintaxe de record
#Expressões record são listas delimitadas por vírgula de campos nomeados ou posicionais, entre parênteses:
var record = ('first', a: 2, b: true, 'last');
Anotações de tipo record são listas delimitadas por vírgula de tipos entre parênteses.
Você pode usar anotações de tipo record para definir tipos de retorno e tipos de parâmetros.
Por exemplo, os seguintes statements (int, int) são anotações de tipo record:
(int, int) swap((int, int) record) {
var (a, b) = record;
return (b, a);
}
Campos em expressões record e anotações de tipo espelham como parâmetros e argumentos funcionam em funções. Campos posicionais vão diretamente dentro dos parênteses:
// Record type annotation in a variable declaration:
(String, int) record;
// Initialize it with a record expression:
record = ('A string', 123);
Em uma anotação de tipo record, campos nomeados vão dentro de uma seção delimitada por chaves de pares tipo-e-nome, após todos os campos posicionais. Em uma expressão record, os nomes vão antes de cada valor de campo, seguidos de dois-pontos:
// Record type annotation in a variable declaration:
({int a, bool b}) record;
// Initialize it with a record expression:
record = (a: 123, b: true);
Os nomes de campos nomeados em um tipo record fazem parte da definição de tipo do record, ou sua forma. Dois records com campos nomeados com nomes diferentes têm tipos diferentes:
({int a, int b}) recordAB = (a: 1, b: 2);
({int x, int y}) recordXY = (x: 3, y: 4);
// Compile error! These records don't have the same type.
// recordAB = recordXY;
Em uma anotação de tipo record, você também pode nomear os campos posicionais, mas esses nomes servem apenas para documentação e não afetam o tipo do record:
(int a, int b) recordAB = (1, 2);
(int x, int y) recordXY = (3, 4);
recordAB = recordXY; // OK.
Isso é similar a como parâmetros posicionais em uma declaração de função ou typedef de função podem ter nomes, mas esses nomes não afetam a assinatura da função.
Para mais informações e exemplos, confira Tipos de record e Igualdade de record.
Campos de record
#Campos de record são acessíveis através de getters integrados. Records são imutáveis, então os campos não têm setters.
Campos nomeados expõem getters com o mesmo nome. Campos posicionais expõem getters
com o nome $<posição>, pulando campos nomeados:
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'
Para simplificar ainda mais o acesso aos campos de record, confira a página sobre Patterns.
Tipos de record
#Não há declaração de tipo para tipos de record individuais. Records são tipados estruturalmente com base nos tipos de seus campos. A forma de um record (o conjunto de seus campos, os tipos dos campos e seus nomes, se houver) determina exclusivamente o tipo do record.
Cada campo em um record tem seu próprio tipo. Os tipos de campo podem diferir dentro do mesmo record. O sistema de tipos conhece o tipo de cada campo sempre que ele é acessado a partir do record:
(num, Object) pair = (42, 'a');
var first = pair.$1; // Static type `num`, runtime type `int`.
var second = pair.$2; // Static type `Object`, runtime type `String`.
Considere duas bibliotecas não relacionadas que criam records com o mesmo conjunto de campos. O sistema de tipos entende que esses records são do mesmo tipo, mesmo que as bibliotecas não estejam acopladas uma à outra.
Igualdade de record
#Dois records são iguais se tiverem a mesma forma (conjunto de campos), e seus campos correspondentes tiverem os mesmos valores. Como a ordem dos campos nomeados não faz parte da forma de um record, ela não afeta a igualdade.
Por exemplo:
(int x, int y, int z) point = (1, 2, 3);
(int r, int g, int b) color = (1, 2, 3);
print(point == color); // Prints 'true'.
({int x, int y, int z}) point = (x: 1, y: 2, z: 3);
({int r, int g, int b}) color = (r: 1, g: 2, b: 3);
print(point == color); // Prints 'false'. Lint: Equals on unrelated types.
Records definem automaticamente os métodos hashCode e == com base na estrutura
de seus campos.
Múltiplos retornos
#Records permitem que funções retornem múltiplos valores agrupados. Para recuperar os valores de um record retornado, desestruture os valores em variáveis locais usando pattern matching.
// Returns multiple values in a record:
(String name, int age) userInfo(Map<String, dynamic> json) {
return (json['name'] as String, json['age'] as int);
}
final json = <String, dynamic>{'name': 'Dash', 'age': 10, 'color': 'blue'};
// Destructures using a record pattern with positional fields:
var (name, age) = userInfo(json);
/* Equivalent to:
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
*/
Você também pode desestruturar um record usando seus campos nomeados,
com a sintaxe de dois-pontos :, sobre a qual você pode ler mais na
página de Pattern types:
({String name, int age}) userInfo(Map<String, dynamic> json)
// ···
// Destructures using a record pattern with named fields:
final (:name, :age) = userInfo(json);
Você pode retornar múltiplos valores de uma função sem records,
mas outros métodos têm desvantagens.
Por exemplo, criar uma classe é muito mais verboso, e usar outros tipos de
coleção como List ou Map perde segurança de tipo.
Records como estruturas de dados simples
#Records apenas armazenam dados. Quando isso é tudo que você precisa, eles estão imediatamente disponíveis e são fáceis de usar sem precisar declarar novas classes. Para uma lista simples de tuplas de dados que têm todas a mesma forma, uma lista de records é a representação mais direta.
Considere esta lista de "definições de botões", por exemplo:
final buttons = [
(
label: "Button I",
icon: const Icon(Icons.upload_file),
onPressed: () => print("Action -> Button I"),
),
(
label: "Button II",
icon: const Icon(Icons.info),
onPressed: () => print("Action -> Button II"),
)
];
Esse código pode ser escrito diretamente, sem nenhuma declaração adicional.
Records e typedefs
#
Você pode optar por usar typedefs para dar um nome ao próprio tipo record,
e usar esse nome em vez de escrever o tipo record completo.
Esse método permite declarar que alguns campos podem ser null (?),
mesmo que nenhuma das entradas atuais da lista tenha valor null.
typedef ButtonItem = ({String label, Icon icon, void Function()? onPressed});
final List<ButtonItem> buttons = [
// ...
];
Como tipos record são tipos estruturais, dar um nome como ButtonItem
apenas introduz um alias que facilita referir-se ao tipo estrutural:
({String label, Icon icon, void Function()? onPressed}).
Fazer com que todo o seu código se refira a um tipo record pelo seu alias facilita mudar depois a implementação do record sem precisar atualizar cada referência.
O código pode trabalhar com as definições de botões fornecidas da mesma forma que faria com instâncias de classe simples:
List<Container> widget = [
for (var button in buttons)
Container(
margin: const EdgeInsets.all(4.0),
child: OutlinedButton.icon(
onPressed: button.onPressed,
icon: button.icon,
label: Text(button.label),
),
),
];
Você pode até decidir mais tarde mudar o tipo record para um tipo de classe, para adicionar métodos:
class ButtonItem {
final String label;
final Icon icon;
final void Function()? onPressed;
ButtonItem({required this.label, required this.icon, this.onPressed});
bool get hasOnPressed => onPressed != null;
}
Ou para um tipo de extensão:
extension type ButtonItem._(({String label, Icon icon, void Function()? onPressed}) _) {
String get label => _.label;
Icon get icon => _.icon;
void Function()? get onPressed => _.onPressed;
ButtonItem({required String label, required Icon icon, void Function()? onPressed})
: this._((label: label, icon: icon, onPressed: onPressed));
bool get hasOnPressed => _.onPressed != null;
}
E então criar a lista de definições de botões usando os construtores desse tipo:
final List<ButtonItem> buttons = [
ButtonItem(
label: "Button I",
icon: const Icon(Icons.upload_file),
onPressed: () => print("Action -> Button I"),
),
ButtonItem(
label: "Button II",
icon: const Icon(Icons.info),
onPressed: () => print("Action -> Button II"),
)
];
Novamente, tudo isso sem precisar mudar o código que usa essa lista.
Mudar qualquer tipo exige que o código que o utiliza tenha muito cuidado para não fazer suposições. Um alias de tipo não oferece nenhuma proteção ou garantia, para o código que o usa como referência, de que o valor aliasado é um record. Tipos de extensão também oferecem pouca proteção. Somente uma classe pode fornecer abstração e encapsulamento completos.
Salvo indicação em contrário, a documentação neste site reflete Dart 3.13.7. Página atualizada pela última vez em 2026-11-12. Ver código-fonte ou relatar um problema.
Encontrou essa página sem tradução ou que precisa de correção? Abra uma issue e nos ajude a manter esse site em PT-BR para ajudar pessoas como você.