Skip to content

Destructuring Parameters

Accepted

Complete for V1 source semantics; field-level destructuring defaults are deferred.

Struct and array destructuring are valid parameter binding forms. They are the same general binding patterns used by local declarations and other binding sites.

Struct Parameters

Struct destructuring is name-based:

fn compile({ temp, output, diag }: *const CompileContext, source: Source) Object!CompileError {
}

fn parse({ allocator = alloc, diag }: *const CompilerContext, source: Source) Ast!ParseError {
}

Destructuring in parameters is a binding convenience, not overload resolution and not an effect system. The parameter type remains the annotated type after the pattern.

_ is valid as a whole parameter binding pattern. It means the parameter exists for the function type and call ABI, but no local binding is introduced:

fn callback(_: Event) void {
}

fn callback(_: Event, _: Context) void {
}

Array Parameters

Array destructuring is position-based:

fn midpoint([x0, y0]: [2]f32, [x1, y1]: [2]f32) [2]f32 {
}

Binding Mutability

Parameter bindings are const by default, including bindings introduced by destructured parameters. Reassignable parameter binding syntax is deferred:

fn parse({ alloc, diag }: *Context) void {
  alloc = other // error
}

var inside a parameter pattern is invalid in V1:

fn parse({ var alloc }: *Context) void {
} // error

Create a mutable local inside the body when needed.

Pointer Sources

For pointer-to-struct parameters, destructuring accesses fields through the pointer using the same rule as ordinary source.field, and binds each requested field under ordinary field-access rules. This includes whatever pointer auto-dereference depth ordinary field access allows; destructuring does not define its own dereference depth.

This is not a context-record feature; it is the normal binding-pattern rule for any pointer-to-struct source:

const CompilerContext = struct {
  alloc: *dyn Allocator
  diag: *Diagnostics
}

fn parse({ alloc, diag }: *const CompilerContext, source: Source) Ast!ParseError {
  // alloc: *dyn Allocator
  // diag: *Diagnostics
}

Conceptually, this behaves like an unnamed parameter plus ordinary field accesses:

fn parse(ctx: *const CompilerContext, source: Source) Ast!ParseError {
  const alloc = ctx.alloc
  const diag = ctx.diag
}

If a requested field would require copying a non-copy value out of the pointee, the binding is rejected. Context-style structs should store borrowed handles such as pointers when the callee should not take ownership.

Borrow field entries may be used when a field should be borrowed instead of copied:

fn inspect({ &diag }: CompilerContext) void {
  // diag: *Diagnostics
}

Destructuring a by-value aggregate parameter does not enable field move-out in V1. Non-copy fields cannot be bound by value through destructuring; keep the parameter whole, use explicit APIs, or borrow the field.

Parameter Defaults

Destructuring defaults apply to the whole parameter value, then the binding pattern destructures that value:

fn inspect({ alloc, diag }: *const CompilerContext = default_context()) void {
}

Field-level defaults inside destructuring patterns are deferred:

fn inspect({ alloc, diag = default_diag() }: *const CompilerContext) void {
} // invalid in V1