> class Foo<T> {
> var ptr: UnsafeMutablePointer<T> {
> deinit {
> ptr.destroy(...)
> ptr.dealloc(...)
> }
> }
>
> init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
> self.count = count
> self.space = count
>
> self.ptr = UnsafeMutablePointer<T>.alloc(count)
> self.ptr.initializeFrom(ptr, count: count)
> }
> }
I don't think this would address the stated goal of pairing initialization and
deinitialization code so that one could not happen without the other.
On the other hand, I could imagine the *init* code also being moved into the
accessor block, and then calling the init would implicitly schedule the deinit.
For instance, suppose you said:
class Foo<T> {
var count: Int, space: Int
var ptr: UnsafeMutablePointer<T> {
init(count: Int, ptr: UnsafeMutablePointer<T>) {
self.ptr = UnsafeMutablePointer<T>.alloc(count)
self.ptr.initializeFrom(ptr, count: count)
}
deinit {
ptr.destroy(count)
ptr.dealloc(space)
}
}
init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
self.count = count
self.space = count
// Swift guarantees that, after a property's init returns, its deinit will
always be called.
// #property() is a placeholder for whatever syntax we use to call
behavior-created methods.
#property(self.ptr).init(count: count, ptr: ptr)
}
}
Advantages include:
1. Initialization is broken up into per-property units, and deinitialization is
paired with those units.
2. If a failable init returns during Phase 1, Swift can tell which properties
have been initialized and need their deinits run. (I believe it's already doing
this kind of tracking for non-custom stuff, like releasing.)
3. After phase 1, all inits have been run and all deinits are enabled, so
there's no need to track anything anymore.
4. This could be used to allow stored properties in same-module extensions; we
would just need to make an exception to access control so that the properties'
initializers would be visible to designated initializers (and their deinits
visible to the type's deinit), even outside their normal scope. Concerns would
not be *quite* as separated as we might like, but it's a lot better than the
"all stored properties in one file" status quo.
5. This could be used to...well, you'll see.
Disadvantages include:
1. Property inits do not know which other properties will have already been
initialized, so they can't access them.
2. There is nothing to indicate the order in which property deinits should be
run.
3. You can probably write multiple inits for a single property, but they'll
still have to share one deinit, so this improves but does not fully solve the
original problem.
4. Not as flexible as partial inits.
5. Not as flexible as a deinit that can schedule arbitrary code blocks which
could vary between inits, and might even be able to be used in other methods.
Disadvantages 1 and 2 might be addressable by introducing a mechanism to
constrain property init ordering. For instance, you could declare dependencies
between properties:
class Foo<T> {
var count: Int, space: Int
// This property's init and deinit can access `count` and `space`, but it can
only be inited
// after them and deinited before them.
@depends(upon: count, space)
var ptr: UnsafeMutablePointer<T> {
init(ptr: UnsafeMutablePointer<T>) {
self.ptr = UnsafeMutablePointer<T>.alloc(space)
self.ptr.initializeFrom(ptr, count: count)
}
deinit {
ptr.destroy(count)
ptr.dealloc(space)
}
}
init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
self.count = count
self.space = count
#property(self.ptr).init(ptr: ptr)
}
}
When compiling a designated initializer, the compiler would simply need to make
sure these ordering rules were not violated. When synthesizing a `deinit`, the
compiler would have to build a property dependency graph and then write code
compatible with it.
(I suppose it would probably still be possible to write a `deinit` by hand; you
would then have to write `#property(self.ptr).deinit()` yourself, and the
compiler would enforce dependencies in reverse.)
It's even possible that *all* properties would have initializers—`count` and
`space` would have `init(count:)` and `init(space:)` initializers,
respectively. Assigning to those properties while uninitialized would just be
syntactic sugar for calling the property initializers, just as assigning to any
other property is syntactic sugar for calling its setter.
* * *
Let's take this further.
It might be possible to use this to synthesize designated initializers,
essentially gaining more control over memberwise inits. For instance, if you
wrote this class:
class Foo<T> {
var count: Int {
init(count: Int = 0) {
self.count = count
}
}
@depends(upon: count) var space: Int {
init() {
space = count
}
}
@depends(upon: count, space) // `count` here is probably redundant.
var ptr: UnsafeMutablePointer<T> {
init(ptr: UnsafeMutablePointer<T> = nil) {
self.ptr = UnsafeMutablePointer<T>.alloc(space)
self.ptr.initializeFrom(ptr, count: count)
}
deinit {
ptr.destroy(count)
ptr.dealloc(space)
}
}
}
The compiler might be able to look at the three property initializers:
init(count: Int = 0)
init()
init(ptr: UnsafeMutablePointer<T> = nil)
And combine their signatures to create `init(count: Int = 0, ptr:
UnsafeMutablePointer<T> = nil)`, then look at the dependency order to
synthesize:
init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
#property(self.count).init(count: count)
#property(self.space).init()
#property(self.ptr).init(ptr: ptr)
}
By extending the rules for synthesizing property inits, this could be taken
further:
1. A property with an initial value would get an `init()`. If that property had
dependencies, it could use those dependencies in the initial value.
2. A property with an initial value, but marked with `@initable`, would get an
`init(propertyName: Type = defaultValue)`.
(These rules could be reversed so that you mark the non-initable properties
instead.)
To make the `init()` easier to use, it would be called automatically if
necessary when you try to initialize another property that depends on it.
With all that in place, we can now write:
class Foo<T> {
@initable var count = 0
@depends(upon: count) var space = count
@depends(upon: count, space)
var ptr: UnsafeMutablePointer<T> {
init(ptr: UnsafeMutablePointer<T> = nil) {
self.ptr = UnsafeMutablePointer<T>.alloc(space)
self.ptr.initializeFrom(ptr, count: count)
}
deinit {
ptr.destroy(count)
ptr.dealloc(space)
}
}
// Synthesized for us:
//
// init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
// self.count = count
// self.ptr = ptr
// }
//
// Or, more explicitly:
//
// init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
// #property(self.count).init(count: count)
// #property(self.space).init()
// #property(self.ptr).init(ptr: ptr)
// }
}
Which is kind of a startling amount of functionality, given where we started.
--
Brent Royal-Gordon
Architechies
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution