Functions and Calls¶
This page defines function signatures, ordinary call binding, rest parameters, call-site unpacking, and collection literal spread.
For a step-by-step introduction, see Functions. For callable traits and callable type sugar, see Callable objects.
Function Signatures¶
Function parameters use name: Type, and return types use -> Type:
def add(a: int, b: int) -> int:
return a + b
Use -> None for a function that does not return a useful value:
def log(message: str) -> None:
println(message)
Ordinary Call Binding¶
Arguments bind to normal parameters in this order:
- Positional arguments bind left to right.
- Named arguments bind by exact parameter name.
- A parameter cannot be bound twice.
- Required parameters that remain unbound are reported as missing.
- Unknown named arguments are rejected unless the callee declares
**kwargs. - Extra positional arguments are rejected unless the callee declares
*args.
def connect(host: str, port: int) -> str:
return f"{host}:{port}"
def main() -> str:
a = connect("localhost", 5432)
b = connect(host="localhost", port=5432)
return a + " " + b
Rest Parameter Mental Model¶
Rest parameters are for APIs that accept "zero or more of the same kind of thing."
The call site stays convenient:
log("started", "listening", "ready")
The callee still receives one ordinary typed value:
def log(*messages: str) -> int:
return len(messages) # messages is List[str]
For keyword rest parameters, the caller writes named options and the callee receives a dictionary:
def annotate(**labels: str) -> int:
return len(labels) # labels is Dict[str, str]
def main() -> int:
return annotate(source="cli", mode="debug")
Coming from Python?
The spelling follows Python, but the contract is more static.
- Python
*argscollects a tuple; Incan*args: TcollectsList[T]. - Python
**kwargscollects a dict; Incan**kwargs: TcollectsDict[str, T]. - Python
**kwargsis often used as an untyped escape hatch; Incan keyword captures are typed. - Python can unpack any iterable or mapping at runtime; Incan only unpacks into fixed parameters when the compiler can prove the length or key set statically.
*exprmeans "positional expansion" and is valid in calls and list literals.**exprmeans "mapping expansion" and is valid in calls and dictionary literals.
When to Use Rest Parameters¶
Use *args when each extra positional value has the same role and type:
def any_true(*checks: bool) -> bool:
for check in checks:
if check:
return true
return false
Use **kwargs when the API intentionally accepts an open set of same-typed named values:
def metric(name: str, value: int, **tags: str) -> int:
return len(tags)
Avoid rest parameters when the names are known and required. Use ordinary parameters:
def connect(host: str, port: int) -> str:
return f"{host}:{port}"
Avoid **kwargs when options have different types or need their own documentation. Use a model:
model RetryOptions:
attempts: int
backoff_ms: int
def fetch(url: str, options: RetryOptions) -> int:
return options.attempts
If the repeated unit is heterogeneous, package it first and make the packaged unit variadic:
model Header:
name: str
value: str
def request(path: str, *headers: Header) -> int:
return len(headers)
Rest Positional Parameters¶
Use *name: T to capture extra positional arguments. Inside the function, name has type List[T].
def sum_all(label: str, *values: int) -> int:
mut total: int = 0
for value in values:
total = total + value
return total
def main() -> int:
return sum_all("scores", 10, 20, 30)
The annotation is the element type, not the container type. Write *values: int, not *values: List[int].
Calling the function with no extra positional arguments is allowed. The binding is an empty list:
def count(*items: str) -> int:
return len(items)
def main() -> int:
return count()
Rest Keyword Parameters¶
Use **name: T to capture unknown named arguments. Inside the function, name has type Dict[str, T].
def request(path: str, **headers: str) -> int:
return len(headers)
def main() -> int:
return request("/status", accept="json", trace="enabled")
The keys are strings derived from the argument names. The annotation is the captured value type, not the container type. Write **headers: str, not **headers: Dict[str, str].
Calling the function with no extra keyword arguments is allowed. The binding is an empty dictionary:
def request(path: str, **headers: str) -> int:
return len(headers)
def main() -> int:
return request("/status")
Combining *args and **kwargs¶
A function may declare both rest forms:
def record(event: str, *values: int, **tags: str) -> int:
return len(values) + len(tags)
def main() -> int:
return record("startup", 1, 2, source="cli", mode="debug")
The rest values are independent:
valuesisList[int]tagsisDict[str, str]
Placement Rules¶
Within one parameter list:
- At most one
*name: Tparameter is allowed. - At most one
**name: Tparameter is allowed. - Normal parameters must appear before any rest parameter.
*name: T, when present, appears after normal parameters.**name: T, when present, must be the last parameter.- Rest parameters cannot have default values.
Valid:
def ok(a: int, b: int, *rest: int, **opts: str) -> int:
return a + b + len(rest) + len(opts)
Invalid:
def bad_order(*rest: int, value: int) -> int:
return value
def also_bad(**opts: str, *rest: int) -> int:
return len(rest) + len(opts)
Call-Site Unpacking¶
Use *expr at a call site to extend the callee's positional rest parameter from an existing ordinary list value:
def sum_all(*values: int) -> int:
mut total: int = 0
for value in values:
total = total + value
return total
def main() -> int:
extra = [2, 3]
return sum_all(1, *extra, 4)
The unpacked expression must typecheck as List[T] for the callee's *name: T parameter.
Use **expr to extend the callee's keyword rest parameter from an existing dictionary:
def request(path: str, **headers: str) -> int:
return len(headers)
def main() -> int:
defaults = {"accept": "json"}
return request("/status", **defaults, trace="enabled")
The unpacked expression must typecheck as Dict[str, T] for the callee's **name: T parameter.
Unpacking can also bind ordinary fixed parameters when the compiler can prove the unpacked value's shape:
def fixed(x: int, y: int) -> int:
return x + y
def needs_rest(*values: int) -> int:
return len(values)
def main() -> int:
ok_fixed = fixed(*[1, 2])
ok_rest = needs_rest(*[3, 4])
return ok_fixed + ok_rest
For fixed positional parameters, the minimum shaped values are tuple expressions and inline list literals. A variable with
type List[T] is still a homogeneous list whose length is not part of the type, so it can feed a *args rest parameter
but cannot silently satisfy a fixed pair such as fixed(x: int, y: int).
For fixed keyword parameters, the minimum shaped value is an inline dictionary literal with string literal keys:
def route(path: str, method: str) -> str:
return f"{method} {path}"
def main() -> str:
return route(**{"path": "/status", "method": "GET"})
An ordinary Dict[str, T] value can feed **kwargs, but it cannot prove that every fixed keyword parameter is present.
List and Dictionary Literal Spread¶
Use *expr inside a list literal to expand an existing ordinary list value into a new list:
def main() -> List[int]:
middle = [2, 3]
return [1, *middle, 4]
List spread preserves source order. Direct elements and spread elements must all be compatible with the resulting list element type.
Use **expr inside a dictionary literal to expand an existing dictionary-like value into a new dictionary:
def main() -> Dict[str, str]:
defaults = {"trace": "off"}
return {**defaults, "trace": "enabled"}
Dictionary spread preserves source order, and later keys overwrite earlier keys just like ordinary dictionary insertion.
That example returns a dictionary whose "trace" value is "enabled".
The destination decides the valid marker:
[*xs]is valid because*expands positional values into a sequence destination.{**xs}is valid because**expands mapping entries into a mapping destination.[**xs]is invalid because a list has no keyword or mapping destination.{*xs}is invalid for dictionary literals; set literal spread is not part of RFC 038.
Source Order and Duplicate Keys¶
Positional unpacking preserves source order. This call:
sum_all(1, *extra, 4)
builds a rest list equivalent to:
[1] + extra + [4]
Keyword rest values and dictionary spread entries are inserted into a dictionary in source order. Duplicate direct named
arguments are rejected, but a duplicate key that arrives through **dict_value follows ordinary dictionary insertion
behavior: later entries replace earlier entries.
def request(path: str, **headers: str) -> int:
return len(headers)
def main() -> int:
overrides = {"trace": "off"}
return request("/status", trace="on", **overrides)
In that example, the captured headers["trace"] value is "off".
Methods¶
Methods support the same rest syntax. The receiver is not part of the rest capture:
class Collector:
def collect(self, *items: int, **labels: str) -> int:
return len(items) + len(labels)
def main() -> int:
collector = Collector()
xs = [1, 2]
labels = {"kind": "demo"}
return collector.collect(0, *xs, **labels)
Function Values¶
Named functions are first-class values. When a function value originates from a rest-aware function, the callable metadata preserves the rest markers, so direct rest arguments and unpack arguments still work through the variable:
def collect(prefix: str, *items: int, **labels: str) -> int:
return len(items) + len(labels)
def main() -> int:
f = collect
xs = [1, 2]
labels = {"kind": "demo"}
return f("event", 0, *xs, **labels)
A plain fixed-arity function type does not become rest-aware just because one of its parameters is a list or dictionary. Rest behavior comes from rest metadata, not from trailing container types alone.
For module-level alternate names such as mean = avg, use a symbol alias instead of a function-local value binding. Symbol aliases are declarations, participate in imports/exports, and preserve alias identity in metadata. See Symbol aliases.
Lowering Model¶
Rest parameters are compile-time sugar over explicit container parameters:
*items: Tlowers to a trailingList[T]parameter.**labels: Tlowers to a trailingDict[str, T]parameter.- Direct rest arguments are pushed into the generated list or inserted into the generated dictionary.
*exprextends the generated list.**exprextends the generated dictionary.- Fixed-parameter unpacking lowers to an ordinary call after the compiler proves the positional length or keyword key set.
- List and dictionary literal spread lower to explicit container construction in source order.
For example:
collect("event", 1, *xs, kind="demo", **labels)
lowers conceptually to a call with explicit rest containers:
collect("event", [1] + xs, <dict containing "kind": "demo" plus labels inserted in source order>)
The emitted Rust uses ordinary Vec and HashMap construction; it does not use runtime reflection or Rust variadics.
Collection literal spread is for runtime list and dictionary expressions; const frozen collection initializers still require direct entries.
Type Errors¶
The compiler reports errors for these cases:
- Extra positional arguments without
*args. - Unknown named arguments without
**kwargs. *exprwhen the callee has no positional rest parameter and the value cannot bind fixed positional parameters.**exprwhen the callee has no keyword rest parameter and the value cannot bind fixed keyword parameters.*exprfor fixed parameters when the value's length is not statically known.**exprfor fixed parameters when the value's string-key set is not statically known.- A direct rest argument whose type is incompatible with the rest element type.
- A
*exprargument whose type is incompatible withList[T]. - A direct keyword rest value whose type is incompatible with the rest value type.
- A
**exprargument whose type is incompatible withDict[str, T]. - Duplicate direct named arguments.
- Duplicate fixed bindings across direct and unpacked arguments.
- Missing required normal parameters.
[**expr]in a list literal.{*expr}in a dictionary literal.
Rust Interop¶
Rest syntax is an Incan call contract. It does not expose C-style variadics and does not automatically apply to arbitrary Rust functions. Rust-backed calls can participate only when the compiler has an Incan-level callable signature that marks the relevant parameter as positional rest or keyword rest.