swiftmemory-addressinout

Why Does Swift Require a Local Copy of Inout Parameters When Using Escaping Closures? (Learning Purposes)


Please note: This swift code it is intended for learning purposes only.

I've just discovered that Swift has pointers like C. I am trying to make this code work, and the only way I did is by making a local copy.

This one is working fine.

func myMult(_ x: Int, _ computeValue: @escaping () -> Int) -> Int { 
    if x == 0 {
        return 0
    } else if x == 1 {
        return computeValue()
    } else {
        return computeValue() + myMult(x - 1, computeValue)
    }
}


func myDelay(_ th: @escaping () -> Int) -> (Bool, () -> Int) {
    return (false, th)  
}


func myForce(_ p: inout (Bool, () -> Int) ) -> Int {
    if p.0 {
        return p.1()
    } else {
        let local_p = p.1
        p.0 = true
        p.1 = { local_p() }
    }
    
    return p.1()
}


// (false, (Function))
var myDelayedResult = myDelay({2 + 3})

// false
myDelayedResult.0
// 5
myDelayedResult.1()

// 1500
myMult(300, {myForce(&myDelayedResult)})

// (true, (Function))
myDelayedResult

Now, if I remove the local_p, I will get an error.

I am studying C, and C has declaring a point and dereferencing a pointer, and also the operator to obtain the memory address of a variable.

ChatGPT explanation:

func myForce(_ p: inout (Bool, () -> Int) ) -> Int {    
    if p.0 {
        return p.1()
    } else {
        p.0 = true
        // error: Escaping closure captures 'inout' parameter 'p'
        p.1 = { p.1() }
    }
    
    return p.1()
}

Now with that in mind, my question is, do I need to dereference p? I tried p.1 = { inout p.1() } but I receive an error.

Found this one Escaping closure captures 'inout' parameter but it still do not answer my question.

Any explanation would be nice.


I posted this as an answer(in a way still a question) but it was taken down, just wanted to continue the implementation and found some1 who would give a pertinent answer so I can mark it as answered.

So here is the edit:


Even with this interesting looking functional thing without inout I still have to use local to a local. I'm not sure if there is a way around it. (BTW I still have no clue how to implement myDelay to be a reference thing e.g array, without using a custom one).

func myMult() -> (Int, @escaping () -> Int) -> Int { 
    return { x, computeValue in
        if x == 0 {
            return 0
        } else if x == 1 {
            return computeValue()
        } else {
            return computeValue() + myMult()(x - 1, computeValue)
        }
    }
}
    

func myDelay() -> (@escaping () -> Int) -> (Bool, () -> Int) {
    return { th in (false, th)}
}


func myForce() -> ((Bool, () -> Int)) -> () -> Int {
    return { (p: (Bool, () -> Int)) in
        var tuple = p
        if tuple.0 {
            return tuple.1
        } else {
            let result = tuple.1()
            // Cannot assign to property: 'p' is a 'let' constant
            // if used without a local
            tuple.0 = true
            tuple.1 = { result }
            return tuple.1
        }
    }
}

var myDelayedResult = myDelay()(){2 + 3}

// false
print(myDelayedResult.0)
// 5
print(myDelayedResult.1())

// 1500
print(myMult()(300, {myForce() (myDelayedResult)()}))

// (false, (Function))
print(myDelayedResult)

Solution

  • I've just discovered that Swift has pointers like C.

    Yes, but it's not related to your code. The near equivalent of a C pointer is UnsafeMutablePointer. As you've discovered inout is in no way a pointer. It is exactly what it says: It copies a value in, and when it returns it copies that value back out into the parameter that was passed. This means that there is, by design, no way to have a inout parameter live beyond the immediate function call. It cannot be captured because by the time needed to copy-out, the target might be gone.

    There is no such thing as "dereferencing" an inout parameter because it is not a pointer.

    (As is common, ChatGPT has given an answer that, if you already know the answer, is vaguely correct and yet is of no help. It is not a particularly useful tool unless you already have a pretty good idea of what the right answer looks like, and even then it is not very trustworthy. It is generally not helpful to post to Stack Overflow the output of ChatGPT.)

    Your code can be written this way to get the "pointer" behavior you want. This is a terrible idea, but it does work, and may provide some insight into how the system works:

    func myForce(_ p: UnsafeMutablePointer<(Bool, () -> Int)> ) -> Int {
        if p.pointee.0 {
            return p.pointee.1()
        } else {
            p.pointee.0 = true
            return p.pointee.1()
        }
    }
    

    I must stress that this is terrible Swift, and that it is very unclear what the goal of your code is. But this does do what your code is trying to do. Your code is absolutely not "functional." A core tenant of functional programming is immutability. So the moment you're doing things inout or var, you've already left the functional road. That in itself is not a problem; Swift is not a functional programming language.

    But what you're doing here is both not FP and also not Swifty. Swift does not particularly like recursion; you can easily crash your program with it and it lacks key optimizations for it. The Swift way to do what you're doing is to build a struct (to represent a value) or possibly an actor (to manage mutable state). Passing around a tuple of (Int, () -> Int) is going to just make your head hurt in Swift. But, as I show, it's definitely possible.