Another aspect of Compile-time Lifetime Dependency Annotations that adopters tend to encounter early on, but isn't directly confronted in the proposal, is their interaction with
BitwiseCopyable. BitwiseCopyable
values have neither ownership nor lifetimes. The compiler is free to create temporary copies as needed and keep those temporary copies around as long as it likes--it has no observable effect on the program. It follows that a lifetime dependence on a BitwiseCopyable
value has no meaning. The compiler has no way to enforce such a dependence, and ignoring the dependence is always valid. To avoid giving programers the wrong impression, the compiler issues an error in code that asks for lifetime dependence on a BitwiseCopyable
type:
func f(arg: Int) -> dependsOn(scoped arg) NonEscapableType // ERROR: invalid lifetime dependence on BitwiseCopyable type
The programmer has asked to "borrow" an integer over the lifetime of NonEscapableType
, which is meaningless because an integer is nothing more than its bitwise representation.
The interaction between BitwiseCopyable and conditionally escapable types leads to conditionally ignored dependence annotations. Conditionally escapable types are introduced in Non-Escapable Types:
struct Box<T: ~Escapable>: ~Escapable {
var t: T
}
// Box gains the ability to escape whenever its
// generic argument is Escapable
extension Box: Escapable where T: Escapable { }
Returning an escapable or conditionally escapable type requires lifetime dependence:
func transfer<T: ~Escapable>(arg: Box<T>) -> dependsOn(arg) Box<T> // 'dependsOn' may be inferred
The compiler ignores this lifetime dependence when it applies to an escapable type like Int
:
func escapingInt() -> Box<Int> {
let box = Box(t: 3)
return transfer(arg: box) // OK: Box<Int> is escapable
}
Adopters do often need to prevent BitwiseCopyable
values from escaping. This requires wrapping the BitwiseCopyable
value in unconditionally ~Escapable
types. @Joe_Groff shows how to do that in Values of ~Escapable type with unlimited, indefinite, and/or unknown lifetime, where UnsafePointer
is BitwiseCopyable
.
public struct Ref<T: ~Copyable>: ~Escapable {
private var address: UnsafePointer<T>
private init<X>(unsafePointer: UnsafePointer<T>, dependingOn dummy: borrowing X) -> dependsOn(dummy) Ref<T> { ... }
public init(borrowing value: borrowing T) -> dependsOn(value) Ref<T> {
// This initializer call is only safe if the programmer passes an `unsafePointer` that correctly
// depends on the lifetime of `value`.
self.init(unsafePointer: _someWayToGetALimitedScopePointer(to: value), dependingOn: value)
}
}
Creating a dependence on a BitwiseCopyable
value is inherently unsafe. But this approach limits unsafety to a single point at which the programmer declares that the BitwiseCopyable
value (unsfePointer
) depends on value
. We could consider formalizing this step with a standard "DependentValue" wrapper.