Escapable parameters to non-escapable initializer

I've been trying to get my head around @_lifetime and ~Escapable. I'm making progress, but I encountered something that has me stumped.

Here's the code in question:

struct Restricted: ~Escapable {
	// error: Cannot borrow the lifetime of 'string', which has consuming ownership on an initializer
	init(_ string: String) {
	}
}

If I'm understanding right, even though this is an initializer, the single parameter default rule applies. So the above code is equivalent to:

struct Restricted: ~Escapable {
	// error: Invalid use of borrow dependence with consuming ownership
	@_lifetime(borrow string)
	init(_ string: String) {
	}
}

Also invalid.

Spelling this out fully, just for the sake of my own understanding, also does not work. But how it fails somehow both seems very logical and surprising at the same time.

struct Restricted: ~Escapable {
	// error: Invalid lifetime dependence specifier on non-existent self
	@_lifetime(self: borrow string)
	init(_ string: String) {
	}
}

Am I making any mistakes here? And, is it is even possible to construct an initializer for a ~Escapable that takes an Escapable parameter? What might my options be?

(if there is any documentation around this, please do point me to it!)

1 Like

initializers have a different default than other parameters. this will compile

struct Restricted: ~Escapable {
	init(_ string: borrowing String) {
	}
}

In the current design, ~E types must depend on some value or access. In this case, the thing to depend on is the string you init it with. However, initializers default to consuming their parameters, so this string doesn't have a lifetime you can depend on. If you imagine lifetimes as tracking dependencies between values, there is no reasonable dependency to track here, since Restricted owns the string due to the consuming convention. Afaik what this diagnostic is attempting to communicate is that the compiler attempted to depend on this parameter, but it doesn't have a lifetime.

Take with pinch of salt I'm not totally fresh on how the current design is supposed to work. The evolution document is probably the best resource. [Pitch #3] Compile-time lifetime dependency annotations

2 Likes

Thank you so much!

This wasn't at all clear to me at first but it's now making a lot more sense.

(and just in case anyone runs into this, I think there's some room for improvement on the feedback specifically for initializers, as noted here: Interaction between non-escapable types - #4 by mattie)

1 Like

When initializing a ~Escapable struct (struct SpanView: ~Escapable), the Swift 6.2+ lifetime checker requires an explicit @_lifetime(...) annotation on init whenever the returned ~Escapable value has no inferred lifetime source from another ~Escapable parameter.

If all parameters to init are Escapable (or if a parameter is only read during construction without being borrowed for the lifetime of self), the compiler assumes self might depend on the local borrow scope of that parameter unless you either:

  1. Add an explicit @_lifetime(borrow source) (or @_lifetime(immortal) if self holds no borrowed references), or
  2. Mark the transient Escapable configuration parameter so the lifetime checker knows self's lifetime is tied strictly to the borrowed storage container rather than the auxiliary argument.