Actor protocols?

I'm not sure how to solve this issue I've run into. My app manages a set of laboratory furnaces by communicating over a serial port with a bunch of hardware controllers to set and get things like temperature. A FurnaceManager actor periodically polls the bus to get the latest temperatures, and updates a FurnaceController actor with the latest values. A FurnaceController has an isEnabled stored property that, when false, basically skips it when polling. All this works fine.

Now I'm trying to make some unit tests for this code, and I need to make two variants of FurnaceController: MODBUSFurnaceController (the real one), and MockFurnaceController (one that fakes its behaviors). So I made a protocol FurnaceController, and added var isEnabled : Bool { get set } to it. Then I tried to implement that with a simple stored property, and ran into

actor-isolated property 'isEnabled' cannot be used to satisfy a protocol requirement 

Obviously I could make a computed property that uses a stored property to back it. But that strikes me as clunky, and makes me question my entire approach here. It seems what I really want is to specify that the protocol is only for actors, but I don't know of any way to do this:

protocol Foo : AnyActor

Since actors can't participate in inheritance, I'm not sure how to support this (substituting implementations at run time).

1 Like

You can require a protocol to be an actor via this syntax:

protocol Foo : Actor { }

1 Like

Oh good to know. Will that then allow the conformance via stored property?

Only a nonisolated stored property can conform to a requirement that is in an : Actor bound protocol.

// As a heads up, distributed actors cannot declare nonisolated stored properties.

It seems to work as expected. At least, the compiler no longer complains.

protocol
FurnaceController : Actor
{
	var enabled			:	Bool				{ get set }
	var	transients		:	FurnaceTransients?	{ get set }
}

actor
MODBUSFurnaceController : FurnaceController
{
	var enabled			:	Bool
	var	transients		:	FurnaceTransients?
}

Huh, I'm actually surprised by that... since it is impossible to use a setter of such protocol:

/tmp/test.swift:11:15: error: actor-isolated property 'int' can not be mutated from a non-isolated context
    await A().int = 12 // async setters are not a thing

Hmm, seems there might be a bug here. Xcode 13.3b3. But it sure seems to me like it should be possible. The call site and the implementation both know enough to allow it to work.

I see the same thing. While the protocol conformance works, and isolated methods can mutate the property, non isolated users can't set the property.

This also begs the question, why hasn't this enhancement been proposed? Manual async setters were deferred due to implementation complexity, but directly setting the proper of an actor seems straightforward.