Generic unowned ref box

Is it possible to make a generic unowned container for existential types? Something like:

public struct UnownedRef<T> {
  unowned let instance: T // T should be proven at compile time to be an AnyObject
}

protocol Service: AnyObject {}

func foo(service: any Service) {
  let unownedRef: UnownedRef<any Service > = UnownedRef(service)
}

Adding generic constraint T: AnyObject leads to compiler error Initializer 'init(_:)' requires that 'any Service' be a class type.

Though, I've managed to do a trick when conforming class can be accessed:

@propertyWrapper
public final class InitedOnDemandShared<P> { // P will be a protocol inherited form AnyObject
  private weak var object: AnyObject?
  private let builder: () -> AnyObject

  public var wrappedValue: Object { ... }

  /// proveSubTyping closure is not called in runtime, it is only needed at completime to prove that U conforms to P protocol
  public init<U>(builder: @escaping () -> U, _ proveSubTyping: (U) -> P) where U: AnyObject {
    self.builder = builder
  }
}

// Usage
@InitedOnDemandShared var service: any Service

_service = InitedOnDemandShared(builder: { ServiceImpClass() }, { $0 })

Any ideas to generic UnownedRef for existentials can be done?

You could just say

func foo(service: any Service) {
  unowned let unownedRef = service
}

No need for a special box in this case.

I know that unowned let unownedRef = service can be written. What I need is to make an array of unowned references for example. Another one problem is that unowned can not be used in protocol declarations:

protocol Assembly {
  var service: any Service { get }
  // unowned var service: any Service { get } // – compiler error
  var service: UnownedRef<any Service> { get } // ok
}

You can use 'some' in the type for the parameter of foo and the UnownedRef.
Like this :

public struct UnownedRef<T: AnyObject> {
    unowned let instance: T // T show be proven at compile time to be an AnyObject
}

protocol Service: AnyObject {
    func printOk()
}

func foo(service: some Service) {
    let unownedRef: UnownedRef<some Service > = UnownedRef(instance: service)
    unownedRef.instance.printOk()
}

class MyClass: Service {
    func printOk() {
        print("ok")
    }
}

let myClass = MyClass()
foo(service: myClass)

I can't use some Service because I get any Service form 3d party SDK.
So the task is to make unowned ref box for existentials. With opaque and generic types it is doable.

protocol Service: AnyObject {}

struct UnownedReference<T: AnyObject> {
  unowned var reference: T
  init(_ reference: T) { self.reference = reference }
}

protocol UnownedServiceReference {
  func get() -> any Service
}

extension UnownedReference: UnownedServiceReference where T: Service {
  func get() -> any Service { self.reference }
}

func makeUnownedReference(_ service: any Service) -> any UnownedServiceReference {
  // We need a function to unwrap the 'any Service' existential
  func makeRef(_ s: some Service) -> some UnownedServiceReference { UnownedReference(s) }
  return makeRef(service)
}

You should be able to make an array of any UnownedServiceReference.

1 Like

This is interesting idea, based on SE-0352, thanks.

I've also played with it and came up with this:

public struct UnownedRef<E> {
  private unowned let _instance: AnyObject
  
  public var wrappedValue: E { _instance as! E }
  
  public init(_ instance: E, prove typeCastToAnyObject: (E) -> any AnyObject = { $0 }) {
    let typeCasted = typeCastToAnyObject(instance)
    assert((instance as AnyObject) === typeCasted)
    self._instance = typeCasted
  }
  
  public init(_ instance: E) where E: AnyObject {
    self._instance = instance
  }
}

protocol Service: AnyObject {}

func foo(_ service: any Service) {
  UnownedRef(service) // OK
}

func bar(_ instance: any CustomStringConvertible, integer: Int) {
  UnownedRef(instance) // compiler error: requires that 'any CustomStringConvertible' be a class type
  UnownedRef(integer) // compiler error: requires that 'Int' be a class type
}

func baz(_ object: NSObject) {
  UnownedRef(object) // OK
  UnownedRef(object as AnyObject) // OK
}