
Every value in Vary has a type known at compile time. The type system catches mismatches before your code runs, so there are no runtime type surprises. Types range from simple primitives like `Int` and `Str` to collections, optionals, function types, and user-defined classes and enums.

## Primitive types

| **Type** | **Example** | **JVM mapping** |
|------|---------|-------------|
| `Int` | `42` | `long` |
| `Float` | `3.14` | `double` |
| `Bool` | `True`, `False` | `boolean` |
| `Str` | `"hello"` | `String` |

## Collection types

| **Type** | **Example** | **JVM mapping** |
|------|---------|-------------|
| `List[T]` | `[1, 2, 3]` | `ArrayList` |
| `Dict[K, V]` | `{"a": 1}` | `HashMap` |
| `Set[T]` | `{1, 2, 3}` | `HashSet` |
| `(T, U, ...)` | `(1, "a")` | `VaryTupleN` |

## Optional types

Append `?` to any type to make it optional. Optional values are either a value of that type or `None`.

```vary-snippet
def find(name: Str) -> Int? {
    if name == "Alice" {
        return 42
    }
    return None
}

let result: Int? = find("Alice")

if result is not None {
    print(result + 1)   # result is Int here
}

let value = result ?: 0       # elvis operator
let upper: Str? = s?.upper()  # safe call
let forced: Int = result!!    # non-null assertion (throws if None)
```

The compiler tracks null safety through control flow. After an `if x is not None` check (or `if x != None`), the variable is narrowed to the non-optional type inside the block. `is None` and `is not None` are the idiomatic identity comparison forms.

## Generics

Classes, data types, and functions can take type parameters:

```vary
class Box[T](value: T) {
    let value: T = value
    def get(self) -> T {
        return self.value
    }
}

let int_box = Box(42)
let str_box = Box("hello")

data Pair[A, B] {
    first: A
    second: B
}

def identity[T](x: T) -> T {
    return x
}
```

Type arguments are inferred from the arguments passed. Generics use type erasure at the JVM level.

## Tuples

Tuples are fixed-size groups of values with potentially different types:

```vary
let point = (1, 2)
let (x, y) = point
print(x)                # 1
```

Mark elements with `?` to make them optional in destructuring:

```vary-snippet
let pair = (1, 2)
let (a, b, c?) = pair
print(c)                # None
```

Tuples support 2 to 8 elements. For larger groupings, use a `data` type.

## F-strings

Use `f"..."` for string interpolation:

```vary
let name = "world"
let msg = f"Hello, {name}!"
print(f"2 + 2 = {2 + 2}")
```
