this is definitely a micro-optimization, but should a memberwise init for a property with the weak storage modifier take its argument __shared or __owned?
public
actor C {}
public
struct S
{
weak var c:C?
public
init(c:__shared C)
{
self.c = c
}
}
what would you recommend using instead of weak var? the struct is being used as a context for a monitoring thread, so it needs to halt when C is deinitialized
While it is true that weak references come with overheads, lots of things can impact performance, and the benefits of expressing lifetime information in the language stretch to more than just performance. So I wouldn't say it is never worth doing.
There is a clear "best" answer in this case - weak references should be __shared (i.e. borrowed). They do not extend the lifetime of the class instance.
Example:
class StoresWeakFoo {
weak var instance: Foo?
init(_ instance: Foo) {
self.instance = instance
}
}
class Foo {
var x: Int = 42
}
No ownership annotation, so using the default __owned/take convention for initialisers:
In the default case, since we are not actually consuming the passed value, we need to release it. That also means callers have to defensively retain the instance before calling the initialiser, unless they have some existing known +1 ownership they can transfer. This is all wasted effort.
By being specific - which is not unsafe or dangerous or fragile, it is just expressing our intention more precisely - we can avoid that release, and callers will not have to defensively retain the instance.
Ideally, C would be responsible for owning that monitoring thread, such that C being deinited is directly the signal for termination of the monitoring thread.
I would counter that, if the fact that the storage is a weak reference is an implementation detail of the type, that you shouldn't indirectly expose that fact in the initializer's convention, since you'd want to change it back if you ever change your object graph around and need to make the type store a strong reference instead.
i don’t understand what you mean by this, the monitoring task periodically reports its status back to C, so it has to capture a reference, either strong or weak, to C. so if the monitoring task holds a strong reference to C, then C will never get deinitialized in the first place.
in my instance, C.deinit sends the signal to halt the monitoring task, so the monitor must hold a weak reference and not a strong reference, so this is not an implementation detail.
Alternatively, the monitoring task can publish into an AsyncSequence that C consumes, allowing the deinit of that sequence to communicate the absence of C.
this would take the weak modifier out of the monitoring context, but (at least with what i am imagining) the stream consumer would still have to capture a weak self in order to write its updates back to self.
This is where we're hurt by the abstract nature of the conversation. It's certainly possible that C requires a separate publishing loop, but I suspect it probably doesn't. However, I can't make that case, because I don't know what it is or how you're trying to use it.