Tuple `as struct`

Based on recently active topics such as Tuples conform to Equatable and Anonymous Structs, I'd like to throw my own pre-pitch onto the table as well: as struct syntax.

It's common for libraries to support functions that accept arbitrary dictionaries, one example being serializers:

func serialize(_ dictionary: [String: Any]) { ... }

serialize(
    [
        "name": "John Doe",
        "age": 40,
        "favoriteMeals": {
            "breakfast": "scrambled eggs",
            "lunch": "reuben",
            "dinner": "carbonara"
        }
    ]
)

This approach has several problems:

  • There's no clean way to enforce at compile-time that that the given dictionary is actually serializable. JSONSerialization performs this check at run-time, and Encodable requires employing type-erasure.
  • Similarly, this approach makes you prone to duplicate keys, something one would either have to explicitly check at run-time, or allow to pass through undetected.

With these issues in mind, I propose new syntactical sugar that enables a tuple declaration to be instantiated as an anonymous struct. For example:

let myValue = (foo: "bar", one: 1, true) as struct
print(myValue.foo) // "bar"
print(myValue.2) // "true"

would desugar into

struct __some_struct {
    var foo: String
    var one: String
    var _2: Bool
}

let myValue = __some_struct(foo: "bar", one: 1, _2: true)
print(myValue.foo) // "bar"
print(myValue._2) // "true"

This would also support limited protocol conformance, transforming the earlier example into something like:

func serialize<T: Encodable>(_ value: T) { ... }

serialize(
    (
        name: "John Doe",
        age: 40,
        favoriteMeals: (
            breakfast: "scrambled eggs",
            lunch: "reuben",
            dinner: "carbonara"
        ) as Encodable struct
    ) as Encodable struct
)

Clearly, this pitch is still quite rough, but I'd love to get feedback from folks here, especially on any language design shortcomings I'm likely missing.

2 Likes

This has already been pitched before: Anonymous Structs

As I mentioned in the opening line, this Pre-Pitch is motivated by that discussion. However, I opted to break this out into its own thread as that one is already quite long (>130 posts) and has been dormant for several weeks now.

Additionally, the syntactical approach and capabilities between the pitches are quite different, with this as struct approach purposely targeting more minimal additions that compose well with current syntax. The approach proposed in Anonymous Structs requires the following grammar changes:

type-closure-expression → { type-capture-list }
type-closure-expression → { statements? }
type-closure-expression → { type-closure-signature in statements? }
type-closure-expression → { type-closure-signature in getter-setter-block }
type-closure-expression → { type-closure-signature in struct declarations? }
type-closure-signature → type-capture-list? closure-parameter-clause throws? function-result?
type-closure-signature → type-capture-list

closure-parameter-clause → ( ) | ( closure-parameter-list ) | identifier-list
closure-parameter-list → closure-parameter | closure-parameter , closure-parameter-list
closure-parameter → closure-parameter-name type-annotation
closure-parameter-name → identifier

type-capture-list → [ type-capture-list-items ]
type-capture-list-items → type-capture-list-item | type-capture-list-item , type-capture-list-items
type-capture-list-item → attributes? type-capture-specifier? expression
type-capture-specifier → var | weak var? | unowned var? | unowned(safe) var? | unowned(unsafe) var?

by contrast, the pitch here only requires the following:

primary-expression → tuple-as-struct-expression
tuple-as-struct-expression → tuple-expression as type? struct

where type? in tuple-as-struct-expression is constrained to a protocol or protocol composition type.

Other differences include:

  • Direct member access support without explicit protocol conformance
  • No support for property wrapper syntax
  • All synthesized properties are var by default

Not sure if it was there originally, but maybe I missed it (sorry). It could still be worth suggesting this as an alternative in that thread though.