Pular para o conteúdo

Desestruturação

Nesta página

A desestruturação liga vários nomes de uma vez ao comparar a forma de um array, struct, record ou enum. A mesma linguagem de padrões aparece nos pontos de binding e, quando indicado, na atribuição a variáveis existentes.

Bindings posicionais

Um padrão de tupla extrai valores por posição em um let. Ele também funciona com uma função que retorna vários valores:

let (lo, hi) = min_max([...]) — desestrutura o retorno de uma função em dois nomes.

06-destructuring-array.zolo
Playground
// Feature: Array destructuring (tuple-pattern)

// Syntax: `let (a, b, c) = [v1, v2, v3]`

// When to use: extract multiple values in one line, simulate

// "tuples" on the value side (in Zolo, tuples are arrays).


// Destructure directly from a literal.

let (a, b, c) = [1, 2, 3]
print(a)  // 1

print(b)  // 2

print(c)  // 3


// Heterogeneous types — works because arrays are dynamic.

let (name, age, active) = ["Alice", 30, true]
print(name)  // Alice

print(age)  // 30

print(active)  // true


// Destructure from a function returning an array.

fn min_max(arr: [int]) -> [int] {
  var lo = arr[0]
  var hi = arr[0]
  for x in arr {
    if x < lo { lo = x }
    if x > hi { hi = x }
  }
  return [lo, hi]
}

let (lo, hi) = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print("lo={lo} hi={hi}")  // lo=1 hi=9


// In loops — destructure each element.

let pairs = [[1, "one"], [2, "two"], [3, "three"]]
for pair in pairs {
  let (n, label) = pair
  print("{n}={label}")
}
// expected:

// 1=one

// 2=two

// 3=three

Bindings de struct em let

Use let Point { x, y } = point quando o valor é sabidamente um Point. campo: local renomeia o binding e .. ignora campos não nomeados. Acrescente um bloco else quando o valor puder não casar:

Padrões de struct diretamente em let, renomeação, .. e a forma falível let … else.

08-destructuring-let.zolo
Playground
// Feature: Struct destructuring in `let` (guarded)

// Syntax: `let Type { field, other: renamed, .. } = expr`

// When to use: extract fields when you KNOW the value is that struct.

// If the value might not match, use `let ... else`, `if let`, or `match`.

// A non-matching value panics: "destructure failed: expected T, got ...".

// Note: pattern-`let` is statement-level (inside fn bodies), like let-else.


struct Point {
  x: int,
  y: int,
}

// Basic: bind by field name — no `else` needed when the type is known.

fn dist2(p: Point) -> int {
  let Point { x, y } = p
  return x * x + y * y
}

let p = Point { x: 3, y: 4 }
print("dist2={dist2(p)}")

// expected: dist2=25


// Rename with `field: new_name`, ignore the rest with `..`.

fn xof(p: Point) -> int {
  let Point { x: px, .. } = p
  return px
}

print("x={xof(p)}")

// expected: x=3


// Fallible source? Keep the `else` form — the else block must diverge.

fn describe(v) {
  let Point { x, .. } = v else {
    print("not a point")
    return
  }
  print("point at x={x}")
}

describe(p)
describe(42)

// expected: point at x=3

// expected: not a point

Padrões de struct também funcionam diretamente em parâmetros de função, lambdas e loops for. Um padrão nomeado implica o tipo do parâmetro; um padrão de record anônimo precisa de anotação explícita:

Padrões nomeados e anônimos em parâmetros, closures e for.

09-destructuring-params.zolo
Playground
// Feature: Struct patterns in binding positions

// Syntax: `fn f(Point { x, y })`, `|{ name }| ...`, `for Point { x } in ...`

// When to use: extract fields right where the value arrives — no boilerplate

// `let` at the top of the body. A named pattern IMPLIES the parameter type

// (`fn dist(Point { x, y })` types the param as Point); anonymous patterns

// in fn params need an annotation (`{ name, .. }: User`).

// A non-matching value panics: "destructure failed: expected T, got ...".


struct Point {
  x: int,
  y: int,
}

struct User {
  name: str,
  age: int,
}

// Function parameter — named pattern, type implied.

fn dist2(Point { x, y }) -> int {
  return x * x + y * y
}

let p = Point { x: 3, y: 4 }
print("dist2={dist2(p)}")

// expected: dist2=25


// Anonymous pattern needs the annotation in fn position.

fn greet({ name, .. }: User) -> str {
  return "hi " + name
}

print(greet(User { name: "Alice", age: 30 }))

// expected: hi Alice


// Closures — the sweet spot: |{ field }| in HOF chains.

fn names_of(us) {
  return us.map(|{ name }| name)
}

let us = [User { name: "A", age: 1 }, User { name: "B", age: 2 }]
let ns = names_of(us)
print("{ns[0]}{ns[1]}")

// expected: AB


// for-in — destructure each element; a non-matching element panics.

fn sum_products(ps) -> int {
  let mut acc = 0
  for Point { x, y } in ps {
    acc = acc + x * y
  }
  return acc
}

print("sum={sum_products([Point { x: 1, y: 2 }, Point { x: 3, y: 4 }])}")

// expected: sum=14

Rest records e spread de struct

..rest captura os campos que não foram nomeados em um record anônimo tipado. Num literal de struct nomeada, ..source copia campos de outro valor; campos nomeados explicitamente têm precedência:

Rest records tipados, parâmetros de record estruturais e Type { campo: valor, ..source }.

10-records-rest-spread.zolo
Playground
// Feature: `..rest` bindings, record types, and struct-literal spread

// Syntax:

//   let Type { field, ..rest } = expr      — rest = record with the remaining fields

//   fn f(r: { name: str, age: int })       — anonymous record type annotation

//   Type { field: v, ..source }            — construction spread: named > spread > default

// When to use: split a struct into "the fields I want" + "everything else",

// pass field subsets without declaring a named struct, or copy-with-changes.

// Note: pattern-`let` is statement-level (inside fn bodies), like let-else.


struct User {
  name: str,
  age: int,
  email: str,
}

// `..rest` collects the fields the pattern did not extract into an

// anonymous record — typed, so rest.age works and rest.name (extracted)

// is a compile-time error (TE102).

fn split(u: User) {
  let User { name, ..rest } = u
  print("name={name}")
  print("rest.age={rest.age} rest.email={rest.email}")
}

let u = User { name: "Alice", age: 30, email: "alice@zolo.dev" }
split(u)

// expected: name=Alice

// expected: rest.age=30 rest.email=alice@zolo.dev


// Records are structural: any struct (or record) with AT LEAST those

// fields can be passed — no named type needed.

fn describe(r: { name: str, age: int }) -> str {
  return "{r.name} ({r.age})"
}
print(describe(u))

// expected: Alice (30)


// Construction spread: start from an existing value, override some fields.

// Precedence: named field > spread value > declared default.

let renamed = User { name: "Bob", ..u }
print("renamed={renamed.name}/{renamed.age}/{renamed.email}")

// expected: renamed=Bob/30/alice@zolo.dev


// Pure copy-construct.

let copy = User { ..u }
print("copy={copy.name}")

// expected: copy=Alice


struct Point {
  x: int,
  y: int,
  z: int = 0,
}

// Round trip: destructure with rest, rebuild with spread.

// `flat` holds { x, y }; the new literal overrides z and spreads the rest.

fn lift(p: Point) -> Point {
  let Point { z, ..flat } = p
  return Point { z: z + 10, ..flat }
}

let lifted = lift(Point { x: 1, y: 2 })
print("lifted={lifted.x},{lifted.y},{lifted.z}")

// expected: lifted=1,2,10


// The rest binding also works in anonymous patterns.

fn contact(u: User) {
  let { email, ..basics } = u
  print("email={email} basics.name={basics.name}")
}
contact(u)

// expected: email=alice@zolo.dev basics.name=Alice

Atribuição a variáveis existentes

Padrões sem let atribuem a variáveis que já existem. O lado direito é avaliado primeiro, portanto (a, b) = (b, a) faz uma troca segura. A atribuição de struct seleciona campos pelo nome e _ descarta uma posição:

Troca, passo de Fibonacci, desempacotamento de retorno, descarte e atribuição nomeada de struct.

11-destructuring-assignment.zolo
Playground
// Feature: Destructuring assignment — re-assign EXISTING variables

// Syntax:

//   (a, b) = expr           — positional; RHS evaluated first (swap works)

//   (a, _) = expr           — `_` discards a slot

//   Point { x, y: py } = p  — by name; shorthand writes `x`, rename writes `py`

// When to use: update several existing variables at once — swaps, loop

// state steps, unpacking a returned pair into locals you already declared.

// This ASSIGNS (vars must exist, use `var`); to BIND new names, use

// `let (a, b) = ...` / `let Point { x } = ...` instead.

// A non-matching struct value panics: "destructure failed: expected T".


fn demo_swap() {
  var a = 1
  var b = 2
  (a, b) = (b, a)
  print("a={a} b={b}")
}
demo_swap()

// expected: a=2 b=1


// RHS-first means the right side reads the OLD values — one-line

// state steps without temporaries (here: a Fibonacci step).

fn demo_step() {
  var x = 1
  var y = 1
  (x, y) = (y, x + y)
  (x, y) = (y, x + y)
  print("fib={y}")
}
demo_step()

// expected: fib=3


// Unpack a call result into existing vars; `_` discards.

fn minmax() -> (int, int) {
  return (3, 9)
}

fn demo_unpack() {
  var lo = 0
  var hi = 0
  (lo, hi) = minmax()
  print("lo={lo} hi={hi}")
  (lo, _) = (42, 99)
  print("lo={lo}")
}
demo_unpack()

// expected: lo=3 hi=9

// expected: lo=42


struct Point {
  x: int,
  y: int,
}

// By name: shorthand `x` writes the var `x`; `x: px` writes `px`.

// Only the fields you NAME are assigned — no need to list them all.

fn demo_named(p: Point) {
  var x = 0
  var py = 0
  Point { x } = p
  Point { y: py } = p
  print("x={x} py={py}")
}
demo_named(Point { x: 3, y: 4 })

// expected: x=3 py=4


// Fields and indexes are valid slots too (any assignable place).

fn demo_places() {
  var p = Point { x: 0, y: 0 }
  (p.x, p.y) = (5, 6)
  print("p={p.x},{p.y}")
}
demo_places()

// expected: p=5,6

Padrões em match

O padrão Type { campo } liga campos em match e if let; .. ignora os campos restantes e campo: local renomeia durante a extração:

Extraindo x e y de Point; renomeando campos; ignorando o restante com ..

07-destructuring-struct.zolo
Playground
// Feature: Struct destructuring via `match`

// Syntax: `match value { Type { field, other } => ... }`

// When to use: extract multiple fields from a struct in a single

// pattern. In Zolo, struct destructuring happens via `match`

// (or `if let`), not directly in `let`.


struct Point {
  x: int,
  y: int,
}

struct User {
  name: str,
  age: int,
  email: str,
}

// Basic destructure — bindings have the same name as the fields.

let p = Point { x: 3, y: 4 }
match p {
  Point { x, y } => print("x={x} y={y}"),
}

// expected: x=3 y=4


// Rename fields during destructure: `field: new_name`.

match p {
  Point { x: px, y: py } => print("px={px} py={py}"),
}

// expected: px=3 py=4


// Ignore fields with `..` (rest pattern).

let u = User { name: "Alice", age: 30, email: "a@x.com" }
match u {
  User { name, .. } => print("name={name}"),
}

// expected: name=Alice


// `if let` — destructure when the pattern matches.

if let User { email, .. } = u {
  print("email={email}")
}

// expected: email=a@x.com


// Multiple fields extracted.

match u {
  User { name, age, .. } => print("{name} ({age})"),
}
// expected: Alice (30)

A desestruturação de enum usa o mesmo match, com a variante em notação de ponto (Enum.Variant(x)). Cada variante vira um braço separado; if let serve para o caminho rápido quando só uma variante importa:

Result.Ok(v) e Result.Err(e) como braços de match; if let para o caminho feliz.

08-destructuring-enum.zolo
Playground
// Feature: Enum destructuring via `match` / `if let`

// Syntax: `match v { Enum.Variant(x) => ... }`

// When to use: extract the payload of an enum variant (Result.Ok,

// Option.Some, events with data, etc.). In Zolo, variants use

// dot-syntax: `MyEnum.Variant`, not `MyEnum::Variant`.


use std::Option
use std::Result

enum Result {
  Ok(int),
  Err(str),
}

enum Shape {
  Circle(float),
  Rectangle(float, float),
  Point,
}

// Destructure each variant with its payload.

let r1 = Result.Ok(42)
let r2 = Result.Err("not found")

match r1 {
  Result::Ok(v) => print("ok={v}"),
  Result::Err(e) => print("err={e}"),
}

// expected: ok=42


match r2 {
  Result::Ok(v) => print("ok={v}"),
  Result::Err(e) => print("err={e}"),
}

// expected: err=not found


// Variants with multiple positional fields.

let s = Shape.Rectangle(10.0, 5.0)
match s {
  Shape::Circle(r) => print("circle r={r}"),
  Shape::Rectangle(w, h) => print("rect {w}x{h}"),
  Shape::Point => print("point"),
}

// expected: rect 10x5


// `if let` — fast path for a single variant.

if let Result::Ok(v) = r1 {
  print("got {v}")
}

// expected: got 42


// `if let` with `else` — useful when it does not match.

if let Result::Ok(v) = r2 {
  print("got {v}")
} else {
  print("no value")
}
// expected: no value

Desafio

Comece com duas variáveis existentes e atualize ambas com uma única atribuição desestruturante. Depois reescreva a atualização como binding let e explique por que as duas formas não são intercambiáveis.

Buscar no Zolo

9 resultados

enespt-br