2

Consider code like this:

func log(_ msg: @autoclosure @escaping () -> Any?) {
    print(msg)
}

class Foo {
    let bar = 3
    let lazy: String

    init() {
        log("bar is \(self.bar)")
        self.lazy = "always late"
    }
}

This does not compile:

Error: 'self' captured by a closure before all members were initialized

Fair enough: even though there clearly is no problem here, the compiler can't be expected to figure out that (arbitrary) closures don't use self in ways that are undefined (yet).

Of course, I can work around this by doing:

let bar = self.bar
log("bar is \(bar)")

But this seems clumsy.

Is there a way to tell the Swift compiler to evaluate the @autoclosure-parameter upfront, that is in essence to ignore @autoclosure?

PS: I copied this signature from XCGLogger. I'm not sure why @autoclosure is needed there.

Raphael
  • 9,779
  • 5
  • 63
  • 94
  • "I'm not sure why @autoclosure is needed" The way _you_ have defined `log`, the `@autoclosure` serves no purpose. — I think your workaround is fine. It's hard to see what the problem is. If you want the goodness of autoclosure, what you are doing is what you have to do. – matt Mar 09 '17 at 17:05
  • 1
    Actually, the way you have defined `log`, it doesn't even log properly. :) See my correction, in my answer below. – matt Mar 09 '17 at 17:16
  • @matt Quite right! The inevitable oversights in MWE-construction... – Raphael Mar 09 '17 at 17:41
  • 3
    The reason for using autoclosure in a logger function is that the argument is evaluated lazily, and not evaluated at all if logging is disabled. Compare e.g. http://stackoverflow.com/questions/26828261/logtrace-equivalent-in-swift-language-iphone, or https://developer.apple.com/swift/blog/?id=4 where that technique is demonstrated for an "assert" function. – The `@escaping` attribute seems to be unnecessary. – Martin R Mar 09 '17 at 17:50
  • Wouldn't `@escaping` also be required for laziness? – BallpointBen Mar 09 '17 at 17:57
  • 1
    @BallpointBen: No. It would only be required if the passed closure is called *after* the log function returns (which is not the case). – Martin R Mar 09 '17 at 17:59
  • @MartinR Makes sense, thanks! – Raphael Mar 09 '17 at 18:02

1 Answers1

1

You can put a trampoline between you and log, like this:

func log(_ msg: @autoclosure @escaping () -> Any?) {
    if let msg = msg() {
        print(msg)
    }
}

func mylog(_ msg: Any?) {
    log(msg)
}

class Foo {
    let bar = 3
    let lazy: String

    init() {
        mylog("bar is \(self.bar)")
        self.lazy = "always late"
    }
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 2
    Inline version: `{ log($0) }("bar is \(self.bar)")` – rob mayoff Mar 09 '17 at 17:23
  • @robmayoff Brilliant. I feel a bit dizzy just looking at it...! :) – matt Mar 09 '17 at 17:28
  • Hm. In a way, that's a different flavor of the same clumsiness -- but a valid approach, still. – Raphael Mar 09 '17 at 17:43
  • @robmayoff Cute, but not very practical. ;) Wouldn't want that to have many times in the code. – Raphael Mar 09 '17 at 17:43
  • 1
    @Raphael The point is that you've no choice. You _can't_ magically pre-close the autoclosure, so you _have_ to work around this _somehow_ — put the mention of `self` after initialization has finished, or do the workaround you're already doing, or use a trampoline to evade the compiler's eagle eye. – matt Mar 09 '17 at 18:19
  • 2
    One can use a capture list to make a closure capture "bar" instead of "self", so this compiles and works as expected: `let clo = { [bar = self.bar] in "bar is \(bar)" } ; log(clo())`, even before self is fully initialized. However, I was not able to do the same with a single statement, e.g. `log({ [bar = self.bar] in "bar is \(bar)" }())` does not compile. – It isn't nice anyway, just wanted to mention it. – Martin R Mar 09 '17 at 18:26
  • @MartinR I've never seen an assignment in a capture list. – matt Mar 09 '17 at 18:28
  • https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html: *"... or a variable initialized with some value (such as delegate = self.delegate!)"*. Example: http://stackoverflow.com/a/37301478/1187415, to bind the value of a variable at the time when the closure is created. – Martin R Mar 09 '17 at 18:34
  • @MartinR Great use case. – matt Mar 09 '17 at 18:55