> 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

Reply via email to