Skip to content

Named Tuples

Named tuples provide field names for tuple elements, improving code readability while maintaining tuple performance and immutability.

Type Declaration

# Define a named tuple type alias
type Point = tuple[x: float, y: float]
type Coordinate = tuple[x: float, y: float, z: float]
type Bounds = tuple[min: int, max: int]

Creating Named Tuples

# Using named fields
pos: Point = (x=1.0, y=2.0)
bounds: Bounds = (min=0, max=100)

# Can mix named and positional (positional must come first)
coord: Coordinate = (1.0, 2.0, z=3.0)

# All positional (when type is known)
p: Point = (1.0, 2.0)  # Fields are named based on type

Accessing Fields

type Point = tuple[x: float, y: float]
pos: Point = (x=1.0, y=2.0)

# Access by name
print(pos.x)  # 1.0
print(pos.y)  # 2.0

# Access by position (0-indexed)
print(pos[0])  # 1.0
print(pos[1])  # 2.0

Unpacking

Named tuples support standard tuple unpacking:

type Point = tuple[x: float, y: float]
pos: Point = (x=3.0, y=4.0)

# Positional unpacking
x, y = pos
print(x, y)  # 3.0 4.0

# With wildcards
first, _ = pos  # Ignore second element

Function Return Types

# Function returning named tuple
def get_bounds() -> tuple[min: int, max: int]:
    return (min=0, max=100)

# Usage
bounds = get_bounds()
print(bounds.min)   # 0
print(bounds.max)   # 100

# Unpacking return value
min_val, max_val = get_bounds()

Function Parameters

type Point = tuple[x: float, y: float]

def distance(p1: Point, p2: Point) -> float:
    dx = p2.x - p1.x
    dy = p2.y - p1.y
    return (dx * dx + dy * dy) ** 0.5

result = distance((x=0.0, y=0.0), (x=3.0, y=4.0))  # 5.0

Anonymous Named Tuples

Named tuple types can be used inline without a type alias:

# Inline named tuple type
def parse_config() -> tuple[host: str, port: int, ssl: bool]:
    return (host="localhost", port=8080, ssl=True)

config = parse_config()
print(f"{config.host}:{config.port}")  # localhost:8080

Pattern Matching

Named tuples work with pattern matching:

type Point = tuple[x: float, y: float]

def describe(point: Point) -> str:
    match point:
        case (x=0.0, y=0.0):
            return "Origin"
        case (x=0.0, y=_):
            return "On Y-axis"
        case (x=_, y=0.0):
            return "On X-axis"
        case (x=x, y=y):
            return f"Point at ({x}, {y})"

# Positional patterns also work
match point:
    case (0.0, 0.0):
        print("Origin")
    case (x, y):
        print(f"Point at ({x}, {y})")

Comparison and Equality

Named tuples compare by value, element by element:

type Point = tuple[x: float, y: float]

p1: Point = (x=1.0, y=2.0)
p2: Point = (x=1.0, y=2.0)
p3: Point = (1.0, 2.0)  # Positional construction

print(p1 == p2)  # True
print(p1 == p3)  # True - same values
print(p1 is p2)  # False - different instances

Immutability

Like regular tuples, named tuples are immutable:

type Point = tuple[x: float, y: float]
pos: Point = (x=1.0, y=2.0)

# ❌ Cannot modify fields
pos.x = 3.0  # ERROR: cannot assign to immutable field

# ✅ Create new tuple with modified values
new_pos: Point = (x=3.0, y=pos.y)

C# Mapping

Named tuples map directly to C# ValueTuple with named elements:

# Sharpy
type Point = tuple[x: float, y: float]
pos: Point = (x=1.0, y=2.0)
print(pos.x)
// C# 9.0
(float x, float y) pos = (x: 1.0, y: 2.0);
Console.WriteLine(pos.x);

// Or with type alias
using Point = (float x, float y);
Point pos = (1.0, 2.0);

Type Compatibility

Named tuples with the same field names and types in the same order are compatible:

type Point2D = tuple[x: float, y: float]
type Coordinate2D = tuple[x: float, y: float]

p: Point2D = (x=1.0, y=2.0)
c: Coordinate2D = p  # OK - same structure

# Different names or order - not compatible
type Position = tuple[x: float, y: float]
type Size = tuple[width: float, height: float]

pos: Position = (x=1.0, y=2.0)
size: Size = pos  # ERROR - different field names

Nested Named Tuples

type Point = tuple[x: float, y: float]
type Line = tuple[start: Point, end: Point]

line: Line = (
    start=(x=0.0, y=0.0),
    end=(x=3.0, y=4.0)
)

print(line.start.x)  # 0.0
print(line.end.y)    # 4.0

Common Patterns

Configuration Objects:

type Config = tuple[host: str, port: int, timeout: int]

def load_config() -> Config:
    return (host="localhost", port=8080, timeout=30)

Multiple Return Values:

def parse_header() -> tuple[status: int, message: str, timestamp: int64]:
    return (status=200, message="OK", timestamp=get_timestamp())

Coordinate Systems:

type Point2D = tuple[x: float, y: float]
type Point3D = tuple[x: float, y: float, z: float]
type RGB = tuple[r: uint8, g: uint8, b: uint8]

Limitations

  • All fields must be named, or none (cannot partially name)
  • Field names must be valid identifiers
  • Cannot have default values (use structs for that)
  • Cannot add methods (use classes/structs for that)
# ❌ Cannot partially name fields
type Mixed = tuple[x: int, int]  # ERROR

# ❌ Cannot have default values
type Point = tuple[x: float = 0.0, y: float = 0.0]  # ERROR

# ✅ Use struct instead for defaults
struct Point:
    x: float = 0.0
    y: float = 0.0

Implementation - ✅ Native - Maps to C# ValueTuple with named elements ((Type1 Name1, Type2 Name2)).*