Passing func as func arg retains self

Hi all,

During a memory leakage analysis I’ve found that view controllers were not being dismissed due to self being strongly retained in closures.

Matter of facts, seems that passing an instance function to a function argument that is itself a closure, seems to retain strongly the object where this function is defined.

It could have made sense to me, until I’ve tried to do the same, but instead using a weak reference to self for passing the func as argument.

Is it an already known and wanted behaviour?

If that’s the case, in my opinion it makes really useless using Swift in a functional way when working in reactive contexts, as dealing daily with closures forces you to manually use on-premise closures that weakly captures self.

Looking forward some feedbacks.

1 Like

I'm not sure if I get your exact use case, but if you do this:

socket.on("event", handler: self.eventHandler)

… this is equivalent to:

socket.on("event") { self.eventHandler($0) }

… then you'll create a retain cycle if self owns the socket, since it registers a closure which strongly captures self. The reason it has to capture self is that the event handler is an instance method, and may in its body use self. It it equivalent to this:

socket.on("event") { Self.eventHandler(self)($0) }

But if you instead do:

socket.on("event") { [weak self] in self?.eventHandler($0) }

… it may seem like it is equivalent, but it is not. It is a no-op which is silently dropping events when self goes away.

I totally see how it must be like this, but I wish there would be syntax to make this a little more ergonomic. This is particularly true when the function takes several arguments. Then it it tedious to keep writing: { [weak self] in self.eventHander($0, $1, $2, $3) } instead of just passing self.eventHandler as a simple function reference.

It is possible to exploit the fact that instance methods really are just curried static functions on the type, and write a wrapper function weakify that takes a static function and an instance (that it captures weakly). You can then replace self.eventHandler with weakify(Self.eventHandler). Notice the uppercase S.

1 Like

Thank you for replying.

I don’t get why it has to strongly capture self if it is just passing the function as it was a value on its own.

So then, why would it captures strongly anyway even though referring to a weak instance?

Passing functions as args was just an experiment for writing more fluent and readable code, otherwise I normally use capture list to avoid retains.

Just leaves me as it is useless to pass funcs as args given the retain drawback, or am I missing something?

Because instance methods are passed self. That parameter has to come from somewhere, so it must be captured.

1 Like

You're passing an instance function, with the intent that it'll be called at some point. Calling an instance function only makes sense if there's an instance to call it on. Hence the strong reference, to ensure your instance lasts long enough to be available to have its instance function called.

"Just leaves me as it is useless to pass funcs as args given the retain drawback," That's not generally true. Passing instance functions as closures is totally fine, and useful in many contexts. It's only an issue when you're passing that closure (the instance function) to something that's owned by that instance.

So in Svein's example: self.socket.on("event", handler: self.eventHandler) would be an issue, because self owns (and retains) the socket, which retains the closure which retains self.

DispatchQueue.main.async(self.eventHandler) would be fine, however, because DispatchQueue.main is not owned by self.

....

Some can tell by looking at the code. I suspect most people who are not expert cannot tell. I wish Xcode can "highlight" problematic retain cycle is created like a word processor highlight misspelling or grammar mistake.

I would say that it is not so straightforward understanding when passing a function reference will cause a retain cycle as it is when referring to an object instance in a closure.

It seems to me that you are assuming all functions are functions. Your assertion works for free-standing functions that are not defined in the context of an object(class/struct/enum). However, instance functions are defined as part of the object, and necessarily require the state of the object instance upon which they are operating. That's why self is needed for instance functions, so the state of the instance is available to the function when it is executed. Thus, the need for the instance to hang around until the instance function is invoked, thus, the strong reference. In my mind, instance functions are not really "functions" in the sense of functional programming, because they rely on state external to the function definition, namely, the state of the object instance upon which it is being invoked.

2 Likes

As an aside, I wish that we could define a pure func with a keyword similar to the way we can define a mutating func, and that these functions are guaranteed by the compiler to be pure. They could be referenced as if they were static (among other guarantees).

But I digress.

3 Likes

It would be great if self (and only self) could automatically become weak by just appending a ? It's a bit magic but would just save a lot of effort.

socket.on("event", handler: self?.eventHandler)

If that was too magic then this would work for self and anything else:

socket.on("event", handler: (weak self)?.eventHandler)

or if possible

socket.on("event", handler: weak self?.eventHandler)
3 Likes

That wouldn't do what you imagine it would. By making the self parameter weak, you'd be creating a curried function that is being passed a weak reference. Since the body of the method is expecting a strong (and non-optional) reference, hilarity would ensue.

A pure function must still be passed self, so they are not equivalent to static (remember, the definition of a pure function is only that for the same set of input arguments it returns the same return value and has no side effects: as self is an argument to the function, you must still have a self to give it). What you want is static functions.

Well, pure functions aren’t well-defined, but often one requires that it is independent of all state, except for explicitly passed arguments, and that it cannot mutate any state, except its return value. However, the exact requirements differ between various schools of thought, and various implementations.

I consider mutations of self to be side effects, and reading of self to be an “outside” state, that the pure function cannot depend on.

In my opinion, pure functions in Swift should:

  • not read or write any global or static mutable state, or any other state not passed explicitly as input arguments
  • not call functions that are not also pure
  • not be overridden by an impure function (but could themselves override impure functions)
  • not perform I/O

I guess they could mutate inout parameters, allocate memory, throw, mutate reference types that are explicitly passed as arguments, call function-private inner functions, even if they’re impure (since their context is pure), etc.

But I digress.

Here are some other thoughts on the subject:

With objects, the implicit self is a parameter to the method, therefore the method is only accessing state that was passed in. One might claim that methods of a reference type cannot be pure if they access any members of the type, but technically, the instance is a parameter.

In my model (and I believe D’s?) a pure instance function, wouldn’t be dependent on the instance at all, but merely be a practical place to put it to avoid free functions, and avoid having to type Self. all the time.

One could argue about semantics, but they are not neither well-defined nor absolute until we create an actual implementation. Different languages have different ideas as to what constitutes “a passed argument”. One could argue other way, but the self certainly isn’t passed explicitly, and from the context of a (possibly impure) call-site inside the same instance, a “pure” function as you describe it would not be independent on call order or be memoizable.

But again, we could perhaps discuss this in another thread? I’m sure here are ample opportunities to bikeshed both naming, syntax, semantics as well as compare against prior art.

The second point is simply not a reasonable definition of a pure function. self is an argument to instance methods. I agree that mutating self in such a function is non-pure, but it is bizarre to suggest that pure instance functions may not read self.

Consider this code:

struct Point {
    var x: Int
    var y: Int

    func translated(deltaX: Int, deltaY: Int) -> Point {
        return Point(x: self.x + deltaX, y: self.y + deltaY)
    }
}

The translated function is obviously pure, by any reasonable definition. It is exactly identical to the following function, which you would allow to be pure:

extension Point {
    static func translated(start: Point, deltaX: Int, deltaY: Int) -> Point {
        return Point(x: start.x + deltaX, y: start.y + deltaY)
    }
}

This is why the request for pure is out of place here: the purity of the function is not relevant, what matters is what the arguments are. Instance functions are passed self as an argument: it's literally their defining characteristic. All instance methods could equally be rewritten as static functions with self as the first argument. There is no meaningful difference between instance and static functions other than this.

There is a meaningful discussion to be had about whether static functions defined on the type currently in scope should be accessible without using the Self keyword. But I think it's a profound mistake to use the word pure here, due to the pre-existing meanings it has within computer science.

However, I think this confusion is deeply on topic for this thread: it is clear that Swift has not clearly enough communicated that by their very nature, instance functions must be passed an implicit first argument of self. Python avoids this by requiring that users write out that parameter, but in general people object to the noise of doing so.

That’s an interesting path to explore.

I take offense at your implication that I am somehow confused or misusing existing terminology simply because pure functions in various languages and various academic papers, has different ideas to the extent it will go to allow state to be accessible and/or mutated.

I understand your point well, I think. And I assume you understand mine. And it’s fine that we have different ideas as to how pragmatic or ideological one could apply the “pureness”. In the strictest sense, a pure function couldn’t even print debug messages, read or write the floating point mode flags (even if the flags are reset before the function exits), etc.

There is a vast corpus written on the subject of pure functions, and I agree that you are well within the established definition. But please accept that also am.

I certainly had no intention to offend or disparage, and I'm very sorry that I did so.

But it's not clear to me that you do understand my point. So allow me to rephrase: I believe that any definition of purity that allows static func x(_ a: A) -> B to be considered "pure" should also allow extension A { func x() -> B } to be considered equally so. Neither definition of "x" is more or less capable of performing any side effect or loading any data. They are exactly equivalent to one another. The only distinctions between the two are: 1) the instance method can be called from within the definition of A without needing to explicitly write self/Self; and 2) the instance method is invoked using dot notation for its first argument. Neither of these is a meaningful semantic difference: the difference is purely syntactic sugar.

I don't believe this disagreement is about ideology or pragmatism around purity. For any definition of purity that discusses what a function may do, instead of the letters we type to invoke it, both instance methods and static functions are identical.

As an entirely concrete matter, the gist you linked above matches this reading by giving this example of a pure instance method that references self.

pure extension UInt {
    func addTen() -> UInt {
        return self + 10
    }
}

Good. Thanks :slight_smile:

I agree. And I don't consider any function that mutates any implicitly available state to be pure in this context. A globally defined pure function should not mutate global state, and a statically defined pure function should not mutate static state. Likewise a pure instance function, should not mutate instance state.

struct SomeStruct {
  var someState: State
  pure func foo(input: Input) -> Output {
    //an implementation that reads or writes self.someState, etc.
  }
}

If this function is considered "pure", I think it should also be considered pure if we remove the first and last line. That is, move both the function and the state variable to the global scope. With your definition it is not (I assume?).

I'm applying a strict definition of "pure", where a "pure" function can only read explicitly passed in arguments, only mutate inout arguments and return a value. In my example, foo can only read input, call other pure functions, and produce an Output.

I realize that this is in the stricter side of the scale, and that there are examples of more "pragmatic" approaches so to speak. Both views can find pre-existing support in both literature as well as implementations. However, I think it is pragmatic to allow pure functions to throw, allocate memory, read and write the floating point mode flag, produce debug output.

Maybe you feel that is is too strict. Maybe you're right. I'd like to explore some examples of real world use case that the various approaches allow.

Terms of Service

Privacy Policy

Cookie Policy