Search code examples
swiftxcodesubscript

same subscript code, when building it with two Separate lines it's working fine, when building it with one line of code, im getting an error , why?


I've add two pictures of my code , that explain both Scenarios at the first scenario the subscript work exactly like its supposed to work, with Two lines of code

at the secound scenario , the code should evaluate same as the two line, but im getting an error for some reason

can you help me figure out WHY?

working image :working image

error image : error image

full code:

class SomeClass {

    var dic = Dictionary<String,(()->String) -> String>()

    subscript(_ s:String) -> (()->String) -> String {

        get{
            return dic[s]!
        }set{
            dic[s] = newValue
        }


    }


}


func createClass(_ s:String) -> SomeClass {

    func foo(_ str:()->String) ->String {
        return str() + " AND " + "Two"
    }
    let sc = SomeClass()
    sc["0"] = foo

    return sc

}


// WORKING TWO LINES

let someClass  = createClass("someStr")["0"]
let str = someClass{"One"} // everything work fine, no errors // prints 'One AND Two'



// ERROR ONE LINE

let str  = createClass("someStr")["0"]{"One"}



// WHY?

Solution

  • Your example:

    let str = createClass("someStr")["0"]{"One"}
    

    is using trailing closure syntax.

    Trailing closure syntax works by including the trailing closure as an additional parameter to a function call. Subscripting an array is really a function call under the hood (to a function called subscript), and Swift is trying to pass that closure as a second parameter to the subscripting call, which is what the error is explaining:

    Cannot subscript a value of type 'SomeClass' with an argument of type '(String, () -> String)'.

    In other words, you can't pass both "0" and the closure {"One"} to the subscripting function.


    There are at least 3 ways to fix this and still put it on one line:

    Option 1: Use an explicit call to pass the closure instead of using trailing closure syntax

    Wrap the closure in () to make the call explicit:

    let str1 = createClass("someStr")["0"]({"One"})
    print(str1)
    

    Option 2: Wrap the createClass("someStr")["0"] in parentheses

    That lets Swift know the subscripting only gets "0" as a parameter and allows trailing closure syntax to work as expected:

    let str2 = (createClass("someStr")["0"]){"One"}
    print(str2)
    

    Option 3: Add .self to the result before the trailing closure syntax:

    That again finishes the subscripting call and avoids the confusion.

    let str3 = createClass("someStr")["0"].self {"One"}
    print(str3)
    

    Personally, I would choose Option 1, because trailing closure syntax is unnecessary syntactic sugar that clearly is not working here.


    Solving the Challenge

    In the comments I asked:

    I agree that the trailing closure syntax is most likely a bug that they could fix, but what I don't understand is why you insist on using trailing closure syntax here. What is so objectionable about wrapping the closure in () to make the call explicit even if it is just to work around a bug in Swift?

    You replied:

    the reason for the insisting is that I'm trying to solve a challenge. actually , this function that return a closure is only one side of it it goes like this

    func Challenge() {
        // Do not edit below this line
         XCTAssertEqual(foo("str1")["str2"]{ "654321" }, "123456")
     }
    

    We've already established that trailing closure syntax is pairing the final closure with the indexing operation, so the trick is to design a class that takes a closure with its indexing operation:

    class SomeClass {
        subscript(_ s: String, closure: () -> String) -> String {
            return String(closure().reversed())
        }
    }
    
    func foo(_ str: String) -> SomeClass {
        return SomeClass()
    }
    
    func Challenge() {
        // Do not edit below this line
        XCTAssertEqual(foo("str1")["str2"]{ "654321" }, "123456")
    }