Используя Swift 5.7.2 и Xcode 14.2, я пытаюсь написать функцию расширения для массива определенного типа, то есть [MyClass]
. Внутри функции я хотел бы иметь возможность использовать инициализатор Array()
для преобразования набора в массив, но я не могу этого сделать. Я получаю следующую ошибку: No exact matches in call to initializer
.
Чтобы смоделировать проблему, я создал небольшую игровую площадку с приведенным ниже кодом, где я просто пытаюсь расширить [Int]
. Кроме того, я понял, что это проблема только при расширении массива, поскольку ошибка не появляется, когда я расширяю только тип Int
.
Мне очень любопытно, почему это происходит, и я надеюсь, что кто-то может помочь мне понять это. Скорее всего, этому есть логическое объяснение.
Расширение [Int] (не работает)
extension [Int] {
func foo() {
let strSet = Set(["a", "b", "c", "a"])
let strArray = Array(strSet) // No exact matches in call to initializer
print(strArray)
}
func bar() {
let strSet = Set(["a", "b", "c", "a"])
let strArray = strSet.map {$0}
print(strArray)
}
}
Расширение Int (отлично работает)
extension Int {
func foo() {
let strSet = Set(["a", "b", "c", "a"])
let strArray = Array(strSet) // Works fine
print(strArray)
}
func bar() {
let strSet = Set(["a", "b", "c", "a"])
let strArray = strSet.map {$0}
print(strArray)
}
}
Не расширение (работает нормально)
func foo() {
let strSet = Set(["a", "b", "c", "a"])
let strArray = Array(strSet)
print(strArray)
}
Посмотрев на более подробное сообщение об ошибке:
Swift.Array:3:23: note: candidate requires that the types 'Int' and 'String' be equivalent (requirement specified as 'Element' == 'S.Element')
@inlinable public init<S>(_ s: S) where Element == S.Element, S : Sequence
^
Swift.RangeReplaceableCollection:3:23: note: candidate requires that the types 'Int' and 'String' be equivalent (requirement specified as 'Self.Element' == 'S.Element')
@inlinable public init<S>(_ elements: S) where S : Sequence, Self.Element == S.Element
Похоже, Swift думает, что вы пытаетесь создать Array<Int>
.
Если вы просто укажете параметр универсального типа, он будет работать так, как задумано:
let strArray = Array<String>(strSet)
Это экземпляр выпуска SR-1789.
Обычно, когда вы находитесь в объявлении/расширении универсального типа, вам разрешено использовать этот тип без универсальных аргументов, и аргументы типа будут выводиться как объявленные вами параметры типа. Как в этих ситуациях:
extension [Int] {
func foo() -> Array { // just writing "Array" works, no need to say Array<Int>
fatalError()
}
}
или,
class Foo<T> {
func foo() -> Foo { // just writing "Foo" works, no need to say Foo<T>
fatalError()
}
}
Однако эта функция кажется в некотором смысле слишком «агрессивной».
Ответ Sweeper описывает то, с чем вы столкнулись, но это не ошибка. Просто так работает язык. В определении или расширении универсального типа имя этого типа относится к специализированной версии, а не к универсальной версии. Иллюстрировано:
struct Generic<Placeholder> { }
extension Generic<Never> {
func generic(generic: Generic) -> Self { generic }
}
(Array
не является чем-то особенным в этом отношении; вы должны отредактировать заголовок вопроса.)
Хотя это не соответствует вашему варианту использования, .init
часто является хорошим решением для определения правильного типа:
extension Generic<Never> {
func generic<T>() -> Generic<T> { .init() }
}
Generic().generic() as Generic<Bool>
То компилируется, при этом использовать имя неспециализированного типа не будет:
extension Generic<Never> {
// Cannot convert return expression of type 'Generic<Placeholder>' to return type 'Generic<T>'
func generic<T>() -> Generic<T> { Generic() }
}
Другой способ избежать необходимости явно указывать тип заполнителя — использовать заполнитель типа:
extension Generic<Never> {
func generic<T>() -> Generic<T> { Generic<_>() }
}
И это то, что вам нужно. Array<String>
, [String]
, Array<_>
и [_]
— ваши варианты.
let stringSet = ["a", "b", "c", "a"] as Set
let stringArray = [_](stringSet)
print(stringArray)
Да. Ни у кого не было времени разбираться или долго думать о билете. Язык всегда работал таким образом; это один из основных мотиваторов для заполнителей типа — это упоминается там как «существующее правило», а не как «ошибка».
Спасибо вам обоим за то, что помогли мне понять это о Swift! :D
Что касается «но это не ошибка», у проблемы есть тег «ошибка», и она не закрыта как «работает, как задумано», поэтому в моей книге это ошибка. Вы говорите, что этот код не компилируется намеренно?