Quite frequently there is a need to manually initialize structs/classes with a number of parameters that makes standard initializer syntax not ideal. Without Xcode autocomplete it's very hard to get order of parameter names right. And even with autocomplete if some of the parameters have defaults, this causes a number of possible initializer signatures to become too big:
struct ButtonNode {
let textColor = UIColor.black
let backgroundColor = UIColor.white
let text = ""
let width: CGFloat
let height: CGFloat
}
// causes error: `width` should come before `height`
let button1 = ButtonNode(height: 100, width: 200)
Even worse, when you already have values defined for arguments in variables with same names as parameter names, you're forced to write this in a quite verbose repetitive way:
let height: CGFloat = 100
let width: CGFloat = 100
let button2 = ButtonNode(width: width, height: height)
Workarounds for this exist, but they require creation of custom operators, significant amount of boilerplate, and lead to obscure type-checking errors if a single little thing goes wrong in the use of operators:
With this type of code, you need to consider that any error reported by the compiler may point to the wrong location or otherwise indicate that the compiler has no idea at all what’s gone wrong. There are plenty of cases where I’ve needed to comment out parameter lines to narrow down the location of the problem.
A possible solution would be to allow a special "record initializer" syntax where order of arguments is not significant inspired by Rust:
// order of parameter names doesn't matter
let button1 = ButtonNode | height: 100, width: 200 |
// no need to repeat parameter names if they match variable names
let height: CGFloat = 100
let width: CGFloat = 100
let button2 = ButtonNode | height, width |
This would be a syntax sugar for an initalizer with matching parameter names and transform to let button2 = ButtonNode(width: width, height: height)
.
Important bikeshedding topic: why |
symbol? The problem with {}
brackets is they are already utilized by trailing closure syntax. It would be quite useful to allow both record syntax and trailing closure syntax to coexist in the same initializer expresion:
struct ButtonNode {
// ... same properties with an additional one:
let onTap: () -> ()
}
let button3 = ButtonNode | height, width | { print("button tapped") }
The next best option would be to use []
brackets for record initializer syntax, but these are already used for arrays and dictionaries and this option might look confusing:
// probably ok:
let button1 = ButtonNode [ height: 100, width: 200 ]
// quite confusing, is `[height, width]` an array?
let button1 = ButtonNode [ height, width ]
This "record initialization" syntax is quite useful by itself, but in addition a "record destructuring syntax" could be considered in a separate proposal (again, inspired by Rust, see "Destructuring Structs" section):
func area(button: ButtonNode) -> CGFloat {
let | height, width | = button
return height * width
}
If desired, another shorthand could be introduced:
func area(button | height, width |: ButtonNode) -> CGFloat {
return height * width
}
func areas(buttons: [ButtonNode]) -> [CGFloat] {
return buttons.map { | height, width | in height * width }
}
This destructuring syntax is also a good fit for pattern matching:
switch button {
case let | height, width | where height == width:
print("square button")
default:
print("rectangular button")
}
Thanks for reading, I hope this pitch makes sense. In my experience this syntax makes code a lot easier to read in Rust and it would be great if Swift allowed this as well.