jtbandes jtbandes - 6 months ago 122
Swift Question

When are argument labels required in Swift?

In answering this question it came about that argument labels were required for a call to

init
. This is normal in Swift.

class Foo {
init(one: Int, two: String) { }
}

let foo = Foo(42, "Hello world") // Missing argument labels 'one:two:' in call


However, stranger forces are at play:

extension Foo {
func run(one: String, two: [Int]) { }
}

foo.run(one: "Goodbye", two: []) // Extraneous argument label 'one:' in call


To use an argument label here it would have to be declared explicitly.

I haven't seen something very thorough explaining all of this in the documentation. For which varieties of class/instance/global functions are argument labels required? Are Obj-C methods always exported and imported with argument labels?

Answer

As of Swift 2.2, the language's defaults for the presence of external argument labels have changed and are now simpler. The default behavior can be summarized as follows:

  • First parameters to methods and functions should not have external argument labels.
  • Other parameters to methods and functions should have external argument labels.
  • All parameters to initializers should have external argument labels.

You can find these rules most concisely in the Swift API Design Guidelines. They're also in the latest version of the Swift book, but you'll have to hunt around there.

These rules are best demonstrated with an example:

func printAnimal(animal: String, legCount: Int) {
    let legNoun = legCount == 1 ? "leg" : "legs"
    print("\(animal) has \(legCount) \(legNoun)")
}

struct Player {
    let name: String
    let lives: Int

    init(name: String, lives: Int) {
        self.name = name
        self.lives = lives
    }

    func printCurrentScore(currentScore: Int, highScore: Int) {
        print("\(name)'s score is \(currentScore). Their high score is \(highScore)")
    }
}

// Argument labels must be included or omitted in exactly the following way
// given the definition of the various objects.
printAnimal("Dog", legCount: 4)
let p = Player(name: "Riley", lives: 3)
p.printCurrentScore(50, highScore: 110)

// None of the following will work
printAnimal(animal: "Dog", legCount: 4)  // Extraneous argument label 'animal:' in call
let q = Player("Riley", lives: 3)  // Missing argument label 'name:' in call
p.printCurrentScore(50, 110)  // Missing argument label 'highScore:' in call

For any parameter to any method or function, you may deviate from the language's default, though the style guide rightly warns you not to do so unless there's a good reason.

To add an external parameter label where there would normally not be one, write it before the local parameter label:

func printAnimal(theAnimal animal: String, legCount: Int) {
    let legNoun = legCount == 1 ? "leg" : "legs"
    print("\(animal) has \(legCount) \(legNoun)")
}

printAnimal(theAnimal: "Dog", legCount: 4)

To remove an external parameter label where there normally would be one, use the special external parameter label _:

func printAnimal(animal: String, _ legCount: Int) {
    let legNoun = legCount == 1 ? "leg" : "legs"
    print("\(animal) has \(legCount) \(legNoun)")
}

printAnimal("Dog", 4)

These "default overrides" will work for any method or function, including initializers.


Swift Evolution proposal SE-0046, "establish consistent label behavior across all parameters including first labels," has been accepted and implemented. This change will simplify the rules further. The new rules can be summarized as follows:

  • All parameters to all methods and functions should have external argument labels

When this change has been released, probably in Swift 3.0, I'll update my answer.