# Tuples and deconstruction

> A function returns several values by declaring a tuple return type and returning a parenthesized list. The caller reads them by name (`t.ok`), or deconstructs them straight into locals (`var (ok, value) = …`). Tuples are how Osy# expresses the `TryParse` shape — and an `out` parameter is the same thing written C#'s way: the compiler turns it into a returned tuple, because a call here can suspend and resume elsewhere, and only a return survives that.

<!-- id: function-tuples · area: function · stability: preview · html: https://osysharp.com/reference/function/tuples/ -->

## Summary        {#summary}
A function that has **two things to say** should not have to invent a type for them. Declare the return as a tuple:

```osy title="two values out of one call" test app=function-tuples
class Parser {
  public (bool ok, decimal value) Try(string s) {
    if (s == "1") { return (true, 1m); }
    return (false, 0m);
  }
}
```

The caller takes both:

```osy title="deconstruct at the call site" test app=function-tuples
decimal Read(string s) {
  var p = new Parser();
  var (ok, value) = p.Try(s);
  return ok ? value : 0m;
}
```

## Signature      {#signature}
```osy syntax
(T1 name1, T2 name2) Fn(…) { … }     // a tuple RETURN type; names are optional
return (expr1, expr2);               // a tuple LITERAL
var (a, b) = Fn(…);                  // DECONSTRUCTION into new locals
(T1 a, T2 b) t = Fn(…);              // …or a typed local holding the whole tuple
t.name1                              // an element by the name written on the type
t.Item1                              // …or by position, 1-based
```

A tuple has at least **two** elements. Element names are optional (`(bool, decimal)` is the same type) and may differ
between declarations.

## Description    {#description}

### `out` parameters are a return   {#out}
C#'s `TryParse` shape writes its extra values back **through parameters**, and you may write it exactly that way:
declare `out` parameters on a function or a class method, and call it with `out var x`, `out int x`, `out x` (an
existing variable) or `out _`. The compiler turns the declaration into a function that RETURNS its result and its outs
as a tuple, and each call into the deconstruction of that tuple — because a call here can **suspend** and resume
later, possibly on the other side of the client/server boundary, and only a return survives that.

```osy title="out, declared and called" test app=function-tuples-out
class Inventory {
  public int Stock = 3;
  public bool TryTake(int wanted, out int left) {
    if (wanted > Stock) { left = Stock; return false; }
    left = Stock - wanted;
    return true;
  }
}

void MinMax(List<int> xs, out int min, out int max) { min = xs.Min(); max = xs.Max(); }

string Report() {
  var inv = new Inventory();
  var said = inv.TryTake(2, out var left) ? "took 2, " + left + " left" : "not enough";
  MinMax(new List<int> { 4, 1, 9 }, out var lo, out var hi);
  return said + "; " + lo + ".." + hi;          // "took 2, 1 left; 1..9"
}
```

C#'s rules hold: every `out` is assigned on every path before the method returns, and is not read before it is
assigned. A call works in any statement, in a `while` condition (asked again on every pass) and in a lambda (per
element). An `async` method cannot declare one (C# refuses that too), and `ref` is not supported.

A class with a `Deconstruct(out …)` takes apart like a tuple — `var (x, y) = point;`, `foreach (var (x, y) in points)`,
and the positional patterns below.

### The identity is the element TYPES, not their names   {#identity}
Two tuples with the same element types are the **same type**, whatever their elements are called — C#'s rule, and
what keeps them interchangeable:

```osy title="one shape, two spellings, one type" test app=function-tuples
(bool ok, decimal value) First() { return (true, 1m); }
(bool success, decimal amount) Second() { return (false, 0m); }

decimal Either(bool pick) {
  var a = First();
  var b = Second();
  a = b;                        // the same type — names are labels, not identity
  return a.ok ? 1m : 0m;
}
```

Names are read at the place they are **written**: `a.ok` works because `a` came from a type spelled with `ok`.

### Reading the elements   {#reading}
By name, or by position. Both address the same slots, so a tuple never has "two ways to be right":

```osy title="by name and by position" test app=function-tuples
decimal Both(string s) {
  var p = new Parser();
  var t = p.Try(s);
  return t.ok == t.Item1 ? t.value : t.Item2;
}
```

### A tuple nobody declared   {#inferred}
A tuple does not have to be written as a type anywhere. `var t = (1, "a")` is a `(int, string)` built from its
elements, exactly as in C# — the compiler gives an inferred tuple its type from its SHAPE, and when the app does write
that shape somewhere (a return type, a typed local) the two are the same type.

```osy title="a tuple with no declaration" test app=function-tuples-inferred
string Describe() {
  var t = (1, "a");                    // (int, string)
  var named = (count: 2, label: "b");  // names written on the literal
  var label = "c";
  var n = 3;
  var inferred = (label, n);           // names taken from the locals, as C# infers them
  return t.Item2 + named.label + inferred.label + t;   // "abc(1, a)" — a tuple writes itself "(1, a)"
}
```

Two tuples compare element by element (`(1, "a") == (1L, "a")` is true, the int widened) and `t.Equals(u)` agrees, so a
tuple works as a `Distinct` or `GroupBy` key. A tuple element may be a value, an entity, or a list or array of one;
`null` alone has no type (C# refuses `var t = (1, null)` too), and a tuple held inside an inferred tuple has no slot —
write the type, `(int, string) t = …;`, for either.

### Taking a tuple apart   {#taking-apart}
Deconstruction reads any tuple, and a `KeyValuePair` into its key and value; `_` takes nothing. Assigning a tuple of
EXISTING variables is the swap:

```osy title="deconstruct, discard, swap" test app=function-tuples-inferred
string Parts() {
  var (n, word) = (1, "x");
  var (_, only) = (2, "y");
  var a = 1;
  var b = 2;
  (a, b) = (b, a);                     // the right side is read first, so this swaps
  (int count, string label) = (3, "z"); // each name with its own type…
  (a, var fresh) = (4, 5);             // …or an existing variable beside a new one
  return n + word + only + a + b + count + label + fresh;   // "1xy413z5"
}
```

### Deconstruction evaluates the call ONCE   {#deconstruction}
`var (ok, value) = p.Try(s)` reads the call a single time into a hidden local, then takes each element from it. That
matters whenever the call does something: writing `p.Try(s).Item1` and `p.Try(s).Item2` would run it **twice**.

### A switch that reads a tuple by position   {#positional}
`(x, y) switch { … }` and `t is (1, var s)` test each element — a constant, a relational test (`> 0`), `null` / `not null`, `_` for
anything — and `var name` binds an element for the arm's value. No arm matching throws, as C#'s switch does.

```osy title="a positional switch" test app=function-tuples-inferred
string Quadrant(int x, int y) => (x, y) switch {
  (0, 0) => "origin",
  (_, 0) => "on the x axis",
  (> 0, var up) => "right, " + up + " up",
  _ => "elsewhere",
};
```

Each arm reads the elements again, so the switched value is a tuple of locals, a local, or a member — a CALL is
refused, naming the fix (put it in a local first), because C# reads it once. An `is` pattern's `var` is read in its
own condition and in the `if` branch it guards; over a class, a `null` value does not match.

### Pairs and tuples the LINQ verbs answer   {#verbs}
A dictionary whose type the source infers iterates its `KeyValuePair`s, and the verbs whose selector-less form answers
a pair or a tuple work without a selector:

```osy title="pairs out of a dictionary and out of LINQ" test app=function-tuples-inferred
string Tally() {
  var words = new List<string> { "b", "a", "b" };
  var counts = words.CountBy(w => w);          // KeyValuePair<string, int>s, in first-seen order
  var s = "";
  foreach (var (word, count) in counts) { s += word + count; }
  foreach (var (i, w) in words.Index()) { s += i; }               // (int Index, string Item)
  foreach (var p in new List<int> { 1, 2 }.Zip(words)) { s += p.First + p.Second; }   // (int First, string Second)
  return s + counts.First();                   // "b2a1" + "012" + "1b2a" + "[b, 2]"
}
```

`AggregateBy(key, seed, f)` folds each key's elements from ONE seed, as C# does — so the seed is written as a literal or
a local (a `new List<int>()` there is refused: C# shares that one instance across every key). `KeyValuePair.Create(k, v)`
makes a pair. A tuple projection over stored rows — `Climb.Select(c => (c.Wall, c.Grade)).ToList()` — reads the
columns in SQL and builds one tuple per row.

## Examples       {#examples}

A parse that reports whether it succeeded, without a nullable and without a wrapper class:

```osy title="the TryParse shape, end to end" test app=function-tuples
class Amounts {
  public (bool ok, decimal value) Parse(string raw) {
    if (raw == "") { return (false, 0m); }
    return (true, 42m);
  }
}

decimal Total(string raw) {
  var a = new Amounts();
  var (parsed, amount) = a.Parse(raw);
  return parsed ? amount : 0m;
}
```

A tuple as a parameter, when a pair travels together:

```osy title="a tuple travelling as one value" test app=function-tuples
decimal Apply((bool ok, decimal value) result) {
  return result.ok ? result.value : 0m;
}
```

## Errors         {#errors}

| What you wrote | What you get |
|---|---|
| `(decimal) Fn(…)` | *a tuple needs at least TWO elements — `(bool ok, decimal value)`. A one-element tuple has nothing to hold that the type itself does not; write the type on its own.* |
| `var (only) = Fn();` | *a deconstruction takes at least TWO names — `var (ok, value) = …`. For one value write `var ok = …` without the parentheses.* |
| `var t = (1, null);` | *this tuple's element 2 is `null`, which has no type of its own (C# refuses `var t = (1, null)` too). Write the tuple's type where it is declared — `(int, string) t = …;` — and the literal will be that type.* |
| `var (a, b) = 5;` | *a 'int' cannot be taken apart with `var (a, b) = …` — that reads a tuple or a KeyValuePair.* |
| `(Next(), 2) switch { (1, var s) => … }` | *this switch's patterns test its value once per arm — C# reads it once, so put it in a local first (`var v = …; v switch { … }`), and it is read once.* |
| `xs.AggregateBy(k, new List<int>(), f)` | *`AggregateBy`'s seed is ONE value that every key starts from — C# evaluates it once and shares it. Write it as a literal or put it in a local first …* |
| `t.valu` | *this tuple has no element 'valu'. Did you mean 'value'? — it holds `ok`, `value`* |
| `bool F(out int x) { return true; }` | *the `out` parameter 'x' must be assigned before the method returns, on every path that reaches this return (C# requires it too, CS0177) …* |
| `Try(s, out var v)` where `bool Try(string s)` | *`Try` declares no `out` parameter for this `out` argument to receive — C# refuses it too (CS1615). Declare it on `Try` …* |
| `TryFoo(s, out var f)` where `bool TryFoo(string s, int n)` | *'TryFoo' takes 'n' as a value, not an `out` — C# refuses an `out` argument for it too (CS1620). …* |
| `void Bump(ref int v)` | *`ref` cannot be declared on a parameter — a parameter passes a value IN, and only the RETURN comes back out. …* |

## See also       {#see-also}
- [Functions (the unit of work)](https://osysharp.com/reference/function/index/) — what a function is, and how it is called
- [Classes](https://osysharp.com/reference/class/index/) — the alternative when a pair wants a name and behaviour of its own
- [Generic classes](https://osysharp.com/reference/class/generics/) — the other way one declaration serves many types
