Kyle-Ye
(Kyle)
December 29, 2025, 4:51pm
1
Background
I was tweaking some S*UI code to fix a bug. But after I delete one opacities.remove(at: 2) code. The compiler just give me an error.
To reproduce it, we have to nest a GCD dispatch with a function like withAnimation and then only use a single Array.remove method.
MInimal reproduce code
import Dispatch
func withAnimation<Result>(_ body: () throws -> Result) rethrows -> Result {
try body()
}
func test() {
var opacities: [Double] = [0, 0.5, 1.0]
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation {
opacities.remove(at: 2) // โ Ambiguous use of 'remove(at:)'
}
}
}
The full compiler error is actually very confusing for me.
โ DemoKit git:(main) swift build
Building for debugging...
/Users/kyle/Downloads/DemoKit/Sources/DemoKit/DemoKit.swift:14:23: error: ambiguous use of 'remove(at:)'
12 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
13 | withAnimation {
14 | opacities.remove(at: 2) // โ Ambiguous use of 'remove(at:)'
| `- error: ambiguous use of 'remove(at:)'
15 | }
16 | }
Swift.Array.remove:3:35: note: found this candidate in module 'Swift'
1 | generic struct Array {
2 | @discardableResult
3 | @inlinable public mutating func remove(at index: Int) -> Element}
| `- note: found this candidate in module 'Swift'
4 |
Swift.RangeReplaceableCollection.remove:3:35: note: found this candidate in module 'Swift'
1 | protocol RangeReplaceableCollection {
2 | @discardableResult
3 | @inlinable public mutating func remove(at position: Self.Index) -> Self.Element}
| `- note: found this candidate in module 'Swift'
4 |
Workaround
It turns out to be a Swift compiler bug for me.
And the workaround is use _ = opacities.remove(at: 2) or use multiple statements here.
import Dispatch
func withAnimation<Result>(_ body: () throws -> Result) rethrows -> Result {
try body()
}
func test() {
var opacities: [Double] = [0, 0.5, 1.0]
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation {
opacities.remove(at: 2) // โ
opacities.remove(at: 2) // โ
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation {
_ = opacities.remove(at: 2) // โ
}
}
}
GH issue: Ambiguous use of 'remove(at:)' error when using Array.remove in nested closure with @discardableResult ยท Issue #86233 ยท swiftlang/swift ยท GitHub
3 Likes
tera
December 29, 2025, 5:15pm
2
How could both of these compile?
@frozen public struct Array<Element> {
@discardableResult
@inlinable public mutating func remove(at position: Int) -> Element
extension Array : RangeReplaceableCollection {
@discardableResult
@inlinable public mutating func remove(at index: Int) -> Element
If I try to do something like this with my own types I am getting an error right away:
Invalid redeclaration of 'remove(at:)'
Kyle-Ye
(Kyle)
December 29, 2025, 5:19pm
3
I can see there only exist one implementation for Array in std.
I suspect the diagnostic we got is not accurate here.
extension Array : RangeReplaceableCollection {
@discardableResult
@inlinable public mutating func remove(at index: Int) -> Element
}
Actually, in the standard library, the setting is a little bit different, one of the methods is defined on Array and one on RangeReplaceableCollection. Like this:
protocol MyRangeReplaceableCollection {
associatedtype Index
associatedtype Element
}
extension MyRangeReplaceableCollection {
mutating func remove(at position: Index) -> Element {
fatalError()
}
}
struct MyArray<Element>: MyRangeReplaceableCollection {
typealias Index = Int
mutating func remove(at position: Int) -> Element {
fatalError()
}
}
The above code compiles fine, but when remove is invoked, an error will occur on the caller's side:
import Dispatch
func withAnimation<Result>(_ body: () throws -> Result) rethrows -> Result {
try body()
}
func test() {
var opacities: MyArray<Int> = .init()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation {
opacities.remove(at: 2) // same error as in OP
}
}
}
1 Like
tera
December 30, 2025, 6:01am
5
Distilling it a bit further:
protocol MyRangeReplaceableCollection {}
extension MyRangeReplaceableCollection {
func remove() -> Int { 42 }
}
struct MyArray: MyRangeReplaceableCollection {
func remove() -> Int { 21 }
}
func myWithAnimation<T>(body: () -> T) -> T { body() }
func myAsyncAfter(execute: () -> Void) { execute() }
func test() {
var opacities = MyArray()
myAsyncAfter {
myWithAnimation {
opacities.remove() // Ambiguous use of 'remove()'
}
}
}
1 Like
tera
December 30, 2025, 8:24am
6
Similar simpler example:
struct MyArray {
func remove() -> Int { 21 }
}
func myWithAnimation<T>(body: () -> T) -> T { body() }
func myAsyncAfter(execute: () -> Void) { execute() }
func test() {
var opacities = MyArray()
myAsyncAfter { // Type of expression is ambiguous without a type annotation
myWithAnimation {
opacities.remove()
}
}
}
Changing the inner code to contain explicit return fixes it:
...
myWithAnimation {
return opacities.remove()
}
...
Yet adding a seemingly unrelated statement breaks it again:
...
myWithAnimation {
print()
return opacities.remove() // Cannot convert value of type 'Int' to closure result type 'Void'
}
...
tera
December 30, 2025, 9:33am
7
Unrelated to the compilation issue, the code above could be simplified to take advantage of delay modifier:
withAnimation(.default.delay(1)) {
...
}
1 Like
rayx
(Huan Xiong)
December 31, 2025, 3:48am
8
tera:
Similar simpler example:
It seems to be the return value of myWithAnimation that confuses compiler (note myAsyncAfter doesn't return value). The following change fixes it in my experiments.
func test() {
var opacities = MyArray()
myAsyncAfter {
- myWithAnimation {
+ _ = myWithAnimation {
opacities.remove()
}
}
}
tera
December 31, 2025, 10:20am
9
Further distillery with generics removed:
func anim(body: () -> Int) -> Int { body() }
func anim(body: () -> Void) { body() }
func test1() { anim { 42 } } // OK, expected
func test2() { anim { 42.0 } } // OK, expected
func test3() { anim { print() } } // OK, expected
func test4() { anim { print(); print() } } // โ Error, unexpected
func test5() { anim { print(); return 42 } } // OK, expected
func test6() { anim { print(); 42 } } // โ Error, unexpected (โ )
(โ optional returns with last expression rule is not yet in the language.)
Same but wrapped in another closure:
func myAsync(execute: () -> Void) { execute() }
func testAsync1() {
myAsync { anim { 42 } } // โ Error, unexpected
}
func testAsync2() {
myAsync { anim { 42.0 } } // OK, expected
}
func testAsync3() {
myAsync { anim { print() } } // OK, expected
}
func testAsync4() {
myAsync { anim { print(); print() } } // OK, expected
}
func testAsync5() {
myAsync { anim { print(); return 42 } } // โ Error, unexpected
}
func testAsync6() {
myAsync { anim { print(); 42 } } // OK, expected
}