Guia 10 de 53
Records, maps e atualizações
Records, maps, projeções e caminhos de campos reutilizáveis.
Nesta página
Records, maps, projeções e caminhos de campos reutilizáveis.
Atualizações imutáveis aninhadas¶
A atualização with avalia a base uma vez e copia a raiz e os records alterados no caminho. Filhos intactos continuam compartilhados. O resultado preserva o tipo nominal; caminhos conflitantes e cópias de recursos lineares são rejeitados.
Pratique na By Example · Programa fonte
// Item 4: copy only the record paths being changed, keeping the same type.
struct Address { city: str, postal_code: str }
struct User { name: str, address: Address, roles: [str] }
fn main() {
let original = User {
name: "João",
address: Address { city: "São Paulo", postal_code: "01000-000" },
roles: ["reader"],
}
let changed = original with { name: "Ana", address.city: "Recife" }
print(original.name)
// expected: João
print(changed.name)
// expected: Ana
print(original.address.city)
// expected: São Paulo
print(changed.address.city)
// expected: Recife
print(changed.address.postal_code)
// expected: 01000-000
// The untouched roles reference is shared, while address was copied.
changed.roles.push("reviewer")
print(original.roles.len())
// expected: 2
let next = changed with { address.postal_code: "50000-000" } with { name: "Beatriz" }
print(next.name)
// expected: Beatriz
}Tipos de map explícitos¶
Escreva tipos de map como #{K: V}. Chaves com campos nomeados sempre descrevem um record, inclusive campos chamados int ou String. Chaves vazias representam um record vazio. Map<K, V> continua disponível.
Pratique na By Example · Programa fonte
// Map types and map values use the same explicit delimiter.
let counts: #{str: int} = #{ ok: 2, failed: 1 }
print(counts["ok"])
// expected: 2
// Field labels never turn a record annotation into a map.
let row: { str: int, User: str, type: bool } = #{ str: 7, User: "Ada", type: true }
print(row.str)
// expected: 7
print(row.User)
// expected: Ada
print(row.type)
// expected: true
type Lookup = #{str: [#{int: str}]}
let nested: Lookup = #{ first: [#{ [1]: "one" }] }
print(nested["first"][0][1])
// expected: oneConstrução contextual¶
Quando um tipo struct exato é esperado, .{ campos } constrói esse struct sem repetir seu nome. Defaults, privacidade e campos obrigatórios continuam valendo. O compilador nunca adivinha uma identidade nominal pelos nomes dos campos.
Pratique na By Example · Programa fonte
// A known nominal context supplies the construction name.
struct Point { x: int = 0, y: int = 0 }
struct Segment { start: Point, end: Point }
fn origin() -> Point { .{} }
fn magnitude(point: Point) -> int { point.x + point.y }
fn main() {
let x = 3
let point: Point = .{ x, y: 4 }
print(magnitude(.{ x: 5 })) // expected: 5
print(point.y) // expected: 4
let line: Segment = .{ start: .{ x: 1 }, end: .{ x: 7, y: 2 } }
print(line.end.x) // expected: 7
let shifted: Point = .{ x: point.x + 10, ..point }
print(shifted.x) // expected: 13
}Caminhos de campos tipados¶
field User::address.city cria um FieldPath<User, str> sem ler uma instância. get lê o campo; set devolve uma atualização imutável e compartilha filhos intactos. Caminhos preservam privacidade e o tipo exato; membros computados e armazenamento linear são rejeitados.
Pratique na By Example · Programa fonte
// Item 20: a typed field path can be reused and its getter is a callback.
struct Address { city: str, postal_code: str }
struct User { name: str, address: Address, roles: [str] }
fn city_path() -> FieldPath<User, str> = field User::address.city
fn main() {
let city = city_path()
let original = User {
name: "Ana",
address: Address { city: "São Paulo", postal_code: "01000-000" },
roles: ["reader"],
}
let moved = city.set(original, "Recife")
print(city.get(original))
// expected: São Paulo
print(city.get(moved))
// expected: Recife
print(moved.address.postal_code)
// expected: 01000-000
let cities = [original, moved].map(city.get)
print(cities[0])
// expected: São Paulo
print(cities[1])
// expected: Recife
// The untouched collection is still shared by the immutable update.
moved.roles.push("reviewer")
print(original.roles.len())
// expected: 2
}Records anônimos independentes¶
Sem um tipo nominal esperado, .{ campos } infere um record anônimo com tipos exatos e acesso normal aos campos. Um struct esperado ainda seleciona construção nominal contextual. Um tipo esperado inválido gera diagnóstico, sem virar silenciosamente um tipo estrutural.
Pratique na By Example · Programa fonte
// Anonymous records keep the exact type of each field.
// A nominal annotation still selects that type's ordinary constructor.
fn main() {
let count = 3
let summary = .{ count, total: 12 }
let next = summary with { total: 15 }
print(summary.count, summary.total)
print(next.total)
// expected: 3 12
// expected: 15
}
main()Atualizações imutáveis calculadas¶
Uma atualização with aceita operadores compostos como value += 1. Ela lê o campo antigo uma vez e calcula a substituição sem alterar o original. Restrições de visibilidade, tipos, caminhos conflitantes e cópias lineares continuam valendo.
Pratique na By Example · Programa fonte
struct Counter { value: int, limit: int? }
let current = Counter { value: 2, limit: nil }
let next = current with { value += 1, limit ??= 10 }
print(current.value) // 2
print(next.value) // 3
print(next.limit) // 10Compreensões de maps¶
Construa maps com entradas for e if dentro de #{ }. Chaves calculadas usam [expressão]: valor. Iteráveis, chaves e valores executam uma vez na ordem escrita; chaves repetidas seguem a substituição normal de maps. Bindings do loop e filtro ficam na própria entrada.
Pratique na By Example · Programa fonte
struct User { id: int, name: str, enabled: bool }
let users = [
User { id: 1, name: "Ada", enabled: true },
User { id: 2, name: "Lin", enabled: false },
]
let names = #{for user in users => [user.id]: user.name}
let active_names = #{for user in users => if user.enabled => [user.id]: user.name}
print(names[2])
print(active_names[1])Projeção tipada de records¶
user.{ id, name } avalia user uma vez e cria um record anônimo com os campos físicos visíveis selecionados. Tipos exatos são preservados. Seleções repetidas, campos inacessíveis ou computados e cópias lineares proibidas são rejeitados.
Pratique na By Example · Programa fonte
// Typed projection selects visible physical fields into an anonymous record.
struct User { id: int, name: str, internal: str }
var acquired = 0
fn make() -> User { acquired += 1; User { id: acquired, name: "Ada", internal: "hidden" } }
let public_user = make().{ name, id }
print(acquired)
print(public_user.id)
print(public_user.name)
let updated = public_user with { id += 4 }
print(public_user.id)
print(updated.id)
let empty = make().{}
print(acquired)
struct Box<T> { value: T, id: int }
macro selected(value) { $value.{ value } }
let integer_box: Box<int> = Box { value: 7, id: 1 }
let text_box: Box<str> = Box { value: "text", id: 2 }
let integer = selected!(integer_box)
let text = selected!(text_box)
print(integer.value)
print(text.value)
fn first() -> int { let unused = ({ return 8; User { id: 0, name: "", internal: "" } }).{ id }; 0 }
print(first())
var total = 0
:outer for n in 0..3 {
let value = ({ if n == 1 { continue :outer }; User { id: n, name: "", internal: "" } }).{ id }
total += value.id
}
print(total)Atualizações aninhadas agrupadas¶
Um grupo dentro de uma atualização with compartilha o prefixo do caminho: address { city: value, visits += 1 } atualiza address.city e address.visits. As regras de cópia, privacidade e conflitos continuam valendo. Os inicializadores executam na ordem escrita.
// Update several fields under the same path, leaving the original value intact.
struct Address { city: str, visits: int }
struct Person { name: str, address: Address }
let original = Person { name: "Ana", address: Address { city: "Recife", visits: 2 } }
let moved = original with {
address { city: "Natal", visits += 1 },
}
print(original.address.city) // expect: Recife
print(moved.address.city) // expect: Natal
print(moved.address.visits) // expect: 3Caminhos e aliases em projeções¶
value.{ campo, caminho.campo as nome } copia os campos armazenados selecionados para um record anônimo. A base é avaliada uma vez. Cada caminho deve ser visível e seguir as regras de cópia existentes. Acessores calculados não podem ser projetados, e os nomes de saída devem ser únicos.
// Prepare a public response without copying internal fields.
struct Address { city: str, delivery_code: int }
struct User { id: int, name: str, address: Address }
let user = User {
id: 7,
name: "Ada",
address: Address { city: "Recife", delivery_code: 42 }
}
let response = user.{ id, name as display_name, address.city as city }
print("{response.id}: {response.display_name}, {response.city}")Padrões de array com sufixo¶
Um ...rest pode aparecer entre os elementos fixos. O prefixo conta a partir do início; o sufixo, a partir do fim. O restante é um novo array e pode ser vazio. A entrada precisa ter espaço para o prefixo e o sufixo.
// Read a framed packet without copying the header into its payload.
let packet = [170, 4, 8, 12, 255]
match packet {
[170, ...payload, 255] => {
print("payload: {payload}")
print("bytes: {payload.len()}")
},
_ => print("invalid frame")
}Padrões de map¶
Cada chave selecionada precisa existir antes de seu valor ser verificado. As chaves são strings literais; os valores aceitam padrões aninhados. Entradas extras são permitidas, e ...options copia as não selecionadas para um novo map. Um valor nil remove a entrada, portanto conta como ausência. Um padrão de map não corresponde a um record anônimo.
// Decode a command and retain options that belong to the handler.
let command = #{"action": "rename", "name": "Ada", "mode": "safe"}
match command {
#{"action": "rename", "name": name, ...options} => {
print("rename to {name}")
print("mode: {options["mode"]}")
},
_ => print("unsupported command")
}Continue aprendendo¶
DOCS / OPINIÃO
Esta página deixou alguma pergunta?
Conte onde a explicação perdeu você. A documentação também faz parte da experiência da linguagem.