# 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 — there are no `out` parameters, 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}

### Why not `out`   {#no-out}
C#'s `TryParse` shape writes its second value back **through a parameter**. Osy# has no `out` (nor `ref`) *to
declare*, and the reason is not stylistic: a call here can **suspend** and resume later, possibly on the other side
of the client/server boundary. A pending write-back into a caller's frame has no meaning once that frame may be gone.
A return survives by construction, so the value comes back as one.

```osy title="the shape `out` would have had" syntax
public bool Try(string s, out decimal v) { … }   // ✗ not supported — see the refusal, which names this page
public (bool ok, decimal value) Try(string s)    // ✓ the same information, as a return
```

⚠ **The built-in `T.TryParse` is the exception, and it is not one you have to remember.**
`decimal.TryParse(s, out var v)` — the CALL, not a declaration of your own — compiles: the compiler rewrites it
into `decimal? v = null; try { v = decimal.Parse(s); } catch { }` ahead of the statement. That is only available for
the stdlib parses; a function of your own that wants to hand back two values returns a tuple, as 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;
}
```

### 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**.

## 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.* |
| a literal whose shape nothing declares | *no tuple type `(decimal, decimal)` is declared in this app, so there is nothing for this literal to be. A tuple shape comes from a DECLARATION — write it as a return type … and the literal will match it.* |
| `t.valu` | *this tuple has no element 'valu'. Did you mean 'value'? — it holds `ok`, `value`* |
| `public bool Try(out string v)` | *`out` is not supported — a parameter passes a value IN, and only the RETURN comes back out … For the `TryParse` shape return a NULLABLE and test it, … or return a small `class`.* |

## 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
