Actor seems not work with ExpressibleByIntegerLiteral

actor BetterInt: ExpressibleByIntegerLiteral {
    private(set) var value: Int = 0
    init(integerLiteral value: Int) {
        self.value = value
    }
}

will report error on init: Non-Sendable parameter type 'Self.IntegerLiteralType' cannot be sent from caller of protocol requirement 'init(integerLiteral:)' into nonisolated implementation

So I add sending keyword after the init parameter

actor BetterInt: ExpressibleByIntegerLiteral {
    private(set) var value: Int = 0
    init(integerLiteral value: sending Int) {
        self.value = value
    }
}

will also report error, but this time report on conformance: Type 'BetterInt' does not conform to protocol 'ExpressibleByIntegerLiteral'

3 Likes

Distilling this down:

protocol P {
    associatedtype T
    init(value: T)
}

class Q {} // non sendable

actor BetterInt: P {
    init(value: Q) {}
    // ❌ Non-Sendable parameter type 'Self.T' cannot be sent from caller of protocol requirement 'init(value:)' into nonisolated implementation
}

Yet, if I remove conformance to P it compiles fine...

actor BetterInt {
    init(value: Q) {} // ✅
}

Shouldn't both of these versions result into an error? Or both compile fine?

2 Likes

Solution 1:

protocol improved_ExpressibleByIntegerLiteral {
    associatedtype IntegerLiteralType: Sendable
    init(integerLiteral: IntegerLiteralType)
}


actor BetterInt: improved_ExpressibleByIntegerLiteral {
    init(integerLiteral: Int) {}
}

ExpressibleByIntegerLiteral should have a fix: mark IntegerLiteralType Sendable

or Solution 2:
just use @preconcurrency before ExpressibleByIntegerLiteral

actor ActorInt : @preconcurrency ExpressibleByIntegerLiteral {
    private(set) var value = 0
    init(integerLiteral value: Int) {
        self.value = value
    }
    static func +=(lhs: isolated ActorInt, rhs: Int) {
        lhs.value += rhs
    }
}

or Solution 3:

actor ActorInt : ExpressibleBySendableIntegerLiteral {
    private(set) var value = 0
    init(integerLiteral value: Int) {
        self.value = value
    }
    static func +=(lhs: isolated ActorInt, rhs: Int) {
        lhs.value += rhs
    }
}


nonisolated
protocol ExpressibleBySendableIntegerLiteral : ExpressibleByIntegerLiteral where IntegerLiteralType : Sendable {}

Example:


//
//  BetterIntExecutable.swift
//  SendableTest
//
//  Created by Jiannan@Apple on 2/22/26.
//

@main
struct BetterIntExecutable {
    nonisolated(unsafe)
    static var      count           = 0
    
    nonisolated
    static let classCount: ClassInt = 0
    
    nonisolated
    static let actorCount: ActorInt = 0
    
    @MainActor
    static func main() async {
        await withTaskGroup(of: Void.self) { group in
            for _ in 0..<100000 {
                group.addTask {
                               count += 1
                          classCount += 1
                    await actorCount += 1
                }
            }
        }
        
        print("nonisolated(unsafe) var Int: \(count)")                  // Should be less or equal than 100000
        print("          class wrapped Int: \(classCount.value)")       // Should be less or equal than 100000
        print("          actor wrapped Int: \(await actorCount.value)") // Should be 100000
    }
}

nonisolated
final class ClassInt : ExpressibleByIntegerLiteral, @unchecked Sendable {
    private(set) var value: Int
    init(integerLiteral value: Int) {
        self.value = value
    }
    static func +=(lhs: ClassInt, rhs: Int) {
        lhs.value += rhs
    }
}

nonisolated
actor ActorInt : ExpressibleBySendableIntegerLiteral {
    private(set) var value: Int
    init(integerLiteral value: Int) {
        self.value = value
    }
    static func +=(lhs: isolated ActorInt, rhs: Int) {
        lhs.value += rhs
    }
}

nonisolated
protocol ExpressibleBySendableIntegerLiteral : ExpressibleByIntegerLiteral where IntegerLiteralType : Sendable {}


Output:

nonisolated(unsafe) var Int: 98795
          class wrapped Int: 99889
          actor wrapped Int: 100000
1 Like

:thought_balloon:
I purely wonder if adding Sendable conformance to _ExpressibleByBuiltinIntegerLiteral in the Standard Library would break the existing code.

Solution 3:

actor ActorInt : ExpressibleBySendableIntegerLiteral {
    private(set) var value = 0
    init(integerLiteral value: Int) {
        self.value = value
    }
    static func +=(lhs: isolated ActorInt, rhs: Int) {
        lhs.value += rhs
    }
}


nonisolated
protocol ExpressibleBySendableIntegerLiteral : ExpressibleByIntegerLiteral where IntegerLiteralType : Sendable {}

@YOCKOW @tera this is tricky to figure out

2 Likes

Of the various solutions posted, @preconcurrency ExpressibleByIntegerLiteral seems the least intrusive. The preconcurrency attribute is a way to signal other developers looking at the code that you've manually verified that the protocol is thread-safe, or that you've worked around any threading issues. Since your conformance consists of an initializer with an Int parameter and has no isolation requirements, it should be safe.

1 Like

Even with this:

actor BetterInt: ExpressibleByIntegerLiteral {
    init (integerLiteral value: Int) {
    ^ Non-Sendable parameter type 'Self.IntegerLiteralType' cannot be sent from caller of protocol requirement 'init(integerLiteral:)' into nonisolated implementation
    }
}

That error baffles me. :confused:

Isn't a value of type Int intrinsically sendable? What exactly is the concurrency issue here? :confused:

Could anyone ease my pain? :slight_smile:

1 Like

Technically what the compiler is saying here is that the associated type IntegerLiteralType of protocol ExpressibleByIntegerLiteral isn't constrained to be Sendable. The only requirements of the IntegerLiteralType type parameter is that it must conform to _ExpressibleByBuiltinIntegerLiteral (which doesn't require Sendable either).

In practice, only the stdlib's integer types can be used for the IntegerLiteralType type parameter (as only stdlib types can conform to _ExpressibleByBuiltinIntegerLiteral), and all of them are Sendable. But the protocol definition theoretically allows for the introduction of a non-sendable conformer to _ExpressibleByBuiltinIntegerLiteral (in which case, the above diagnostic would be correct!).

It doesn't seem very useful to have a non-sendable integer, though. Maybe _ExpressibleByBuiltinIntegerLiteral is just missing a Sendable annotation?

4 Likes

I think this really getting at the core of the issue. The compiler is looking at the protocol alone. And it is correct that a type could be used that is unsafe. It is just not taking in account the specific type of being used in this conformance.

This is an artificial limitation. This error is protecting against a situation that is impossible. But I do not know what the implications or difficultly level would be in changing this.

Personally, I think that @frogcjn ‘s solution 3 is the most reasonable, even though I agree it is tricky. Making actors conform to protocols with synchronous requirements is generally a difficult thing to do. But I like this option best, because it is truthfully expressing what you really want. It’s just a shame it needs to be done at all.

1 Like

This seems to be the core of the issue, and I think it's a good idea for a Language Evolution pitch.

Could anyone explain why I'm getting an error in one case but not another?

Why I can not pass a non-isolated type in one case but can pass in another?

Not certain, but I think this has to do with the “special rule” applied to actor initializers:

Actor initializers have a special rule that requires their parameter values to be sent into the actor instance's isolation region. Actor initializers arenonisolated, so a call to an actor initializer does not cross an isolation boundary, meaning the argument values would be usable in the caller after the initializer returns under the standard region isolation rules. SE-0414 consider actor initializer parameters as being sent into the actor's region to allow initializing actor-isolated state with those values

1 Like

Ok, I'll ask:

Is this purely a thought exercise, or is there something specific you're trying to achieve by making an integer-literal-initializable actor?

9 Likes

I think the answer here is that the problem is not the function, but the way the compiler is validating the concurrent safety of the conformance.

1 Like

Example is here.
Just want to make a BetterInt actor more like a normal Int.

By pure coincidence I just ran into this exact limitation in a real project.

2 Likes

Upon doing more research here, I made a bizarre discovery. I had the same problem, so I attempted to use the same solution of an intermediary protocol. And it did not work.

I’ve narrowed the problem down and it is kind of incredible. The protocol trick only works a) when it is explicitly nonisolated and b) only for init requirements. I cannot find any other combination where it will successfully compile.

// with inits
protocol InitRequirement {
	associatedtype Value

	init(_ v: Value)
}

actor BadInit: InitRequirement {
	init(_ v: Int) { // error
	}
}

protocol SendableInitRequirement: InitRequirement where Value: Sendable {}

actor Bad2Init: SendableInitRequirement {
	init(_ v: Int) { // error
	}
}

nonisolated protocol NonisolatedSendableInitRequirement: InitRequirement where Value: Sendable {}

actor GoodInit: NonisolatedSendableInitRequirement {
	init(_ v: Int) { // works???
	}
}

// with anything else
protocol NoninitRequirement {
	associatedtype Value

	func function(_ v: Value) async
}

actor BadFunction: NoninitRequirement {
	func function(_ v: Int) async { // error
	}
}

nonisolated protocol NonisolatedSendableNoninitRequirement: NoninitRequirement where Value: Sendable {}

actor GoodFunction: NonisolatedSendableNoninitRequirement {
	func function(_ v: Int) async { // error???
	}
}

Honestly, I’m amazed this bug was even found. The circumstances required to stumble upon a working version of this are impressive.

Anyways, the short answer is that I think an @preconcurrency conformance is the only viable and general-purpose workaround. I really don’t like it, and I have not fully validated that the runtime behavior is desirable, because this does insert runtime isolation checks. But at least it compiles.

(And even at that, I think the protocol definition must live in a different module for a @preconcurrency conformance to be usable!)

Edit: Actually no, the issue is the protocol must not use concurrency features. Doing that makes it ineligible for a @preconcurrency conformance. In that case I have not yet been able to find a workaround.

Just to make this more concrete, here’s the actual reduced situation I’m facing:

protocol AsyncRequirement {
	associatedtype Value

	nonisolated func use(_ value: Value) async
}

actor UsesValue: AsyncRequirement  {
	// error: Non-Sendable parameter type 'Self.Value' cannot
	func use(_ value: Int) async {
	}
}

The intermediary protocol trick does not work, and neither does a @preconcurrency conformance. Anyone have any ideas that do not involve making Value sendable?

nonisolated protocol AsyncRequirement {
associatedtype Value: Sendable

func use(\_ value: Value) async

}

actor UsesValue: AsyncRequirement {
func use(_ value: Int) async {
}
}

Isn't the implementing function in the actor missing a nonisolated keyword?