Tuples

    A tuple is an ordered, fixed-size collection of values of possibly different types.
    They are lightweight and useful for grouping, returning, or destructuring multiple values without defining a struct.

    Tuples are written using parentheses, with elements separated by commas:

    var t = (10, 20);
    

    You can access tuple elements using numeric indices starting from 0:

    printf("%d\n", t.0); // prints 10
    printf("%d\n", t.1); // prints 20
    

    Returning Tuples from Functions

    Functions can return multiple values by specifying a tuple type as the return type:

    fn pair(x: int, y: int) (int, int) {
        return (x, y);
    }
    
    pub fn main() {
        var (a, b) = pair(10, 20);
        printf("%d %d\n", a, b);
    }
    

    Tuple Destructuring

    Tuples can be destructured into individual variables in a single declaration. Each element in the tuple pattern receives the corresponding element from the right-hand side.

    pub fn main() {
        var (x, y) = (10, 20);
        printf("%d %d\n", x, y); // prints 10 20
    }
    

    Nested Tuples

    Tuples can contain other tuples. Destructuring works recursively and matches the shape of the right-hand side:

    pub fn main() {
        var (x, (y, z)) = (1, (2, 3));
        printf("%d %d %d\n", x, y, z); // prints 1, 2, 3
    }
    

    This form can be arbitrarily nested and follows the same destructuring logic for all levels.

    Explicit Tuple Types

    You can explicitly annotate tuple types to make destructuring clearer or enforce type constraints:

    var (a, (b, c)): (int, (int, char*)) = (10, (20, "Cyrus"));
    
    printf("%d\n", a); // 10
    printf("%d\n", b); // 20
    printf("%s\n", c); // Cyrus
    

    Type annotations ensure the tuple on the right-hand side matches the declared structure.

    Mutability

    By default, the leading keyword (var or const) applies to the entire tuple. However, Cyrus allows you to override the mutability of specific elements within the pattern.

    The var keyword makes all destructured elements mutable, while const makes all of them immutable:

    const (x, y) = (5, 6); // both x and y are immutable
    var (p, q) = (7, 8);   // both p and q are mutable
    

    Mixed mutability:

    var (const a, b, c) = (1, 2, 3);
    // a is const (immutable)
    // b and c are var (mutable)
    
    const (a, var b, c) = (1, 2, 3);
    // a and c are const (immutable)
    // b is var (mutable)
    

    Type Annotation

    In addition to mixed mutability, you can explicitly annotate the type of individual elements within a destructuring pattern. This is particularly useful when you want to enforce a specific type.

    const (var a: int32, (var b: int32, c: int32)) = (1, (2, 3));
    
    // a is a mutable int32
    // b is a mutable int32
    // c is an immutable int32 (inherits const from the root)
    

    Restrictions

    • Tuple destructuring is only allowed inside functions or local scopes.
    • Destructuring without a right-hand side (e.g., var (a, b);) is not allowed, because tuple exports without initialization are meaningless in Cyrus.

    Example of invalid code:

    var (a, (b, c)); // tuple export without rhs is not allowed
    
    • Tuple destructuring is only allowed inside functions or local scopes.
    • Initialization required: Destructuring without a right-hand side (e.g., var (a, b);) is not allowed.
    • Keyword Redundancy: You cannot repeat the leading mutability keyword inside the tuple pattern. This is considered invalid syntax to keep the grammar clean.
    const (const a, _) = (1, 2); // ERROR!: leading keyword cannot be repeated inside
    var (var a, _) = (1, 2);     // ERROR!: leading keyword cannot be repeated inside
    
    var (a, (b, c));             // ERROR!