Swift 5.9'dan önce, geliştiriciler heterojen tür koleksiyonları üzerinde çalışan genel kod yazarken önemli bir ifade kısıtlamasıyla karşılaşmışlardı. Farklı, korunmuş türlerde değişken sayıda argüman gerektiren fonksiyonlar, derleme zamanı güvenliğinden feragat ederek ve yığın tahsis yükü getirerek Any ya da varlık konteynerlerine (any P) başvurmak zorunda kalıyordu. Parametre Paketleri'nin (SE-0393, SE-0398 ve SE-0399) tanıtılması, Swift'e varyadik genel türleri kazandırdı ve dilin, daha önce C++ şablon metaprogramlama ya da Rust varyadik özelliklerini gerektiren kalıpları ifade etmesine olanak tanıdı. Bu evrim, tür güvenli, sıfır maliyetli soyutlamaları heterojen veri üzerinde sağladı ve manuel aşırı yük üretimini ortadan kaldırdı.
Temel zorluk, her biri farklı tür olabilecek bir dizi genel argümanı kabul edebilen bir mekanizmanın uygulanmasında yatıyordu; hem çağrı zinciri boyunca statik tür bilgisinin korunması gerekiyordu. [Any] kullanan pre-parametre paketi çözümleri, çalışma zamanı dönüştürme gerektiriyor ve tür ilişkilerini koruyamadığı için, derleyici optimizasyonlarını önlüyordu. Alternatif olarak, 1'den N'ye kadar olan ariteler için manuel aşırı yük yaratmak (örn. <T1>, <T1, T2>, <T1, T2, T3>) ikili şişkinliğe neden oluyor ve argüman sayılarında keyfi sınırlamalar getiriyordu. Çözüm, derleme zamanı paket yinelemesini desteklemeli, böylece derleyici her çağrı noktasının tür imzasına özel monomorfize kod üretmeliydi; basit değer türleri için çalışma zamanı kutulama ya da tanık tablosu dolayımını ortadan kaldırmadan.
Swift, kod üretimi için derleme zamanı şablonu olarak repeat each T desenini kullanarak paket genişletme aracılığıyla parametre paketlerini uygular. Bir fonksiyon bir tür parametre paketi <each T> bildirir ve bir değer paketi repeat each T kabul ederse, derleyici çağrı noktasında monomorfizasyon gerçekleştirir; genel gövdeyi paketteki her eleman için somut koda dönüştürür. Bu, her elemanın kendi tür kimliğini koruması nedeniyle homojen varyadiklerden (örn. Int...) ayrılmaktadır. repeat anahtar kelimesi, ardından gelen ifadenin her paket öğesi için çoğaltılacağını SIL (Swift Ara Arayüz Dili) oluşturma aşamasına bildirir ve türler buna göre değiştirilir. Bu dönüşüm, değer türlerinin somut düzeninde yığında kalmasının sağlanmasıyla kutulamayı ortadan kaldırır ve fonksiyon çağrıları, varlık konteyneri yükü olmadan statik olarak yönlendirilir.
// Heterojen bir parametre paketini kabul eden fonksiyon func describeValues<each T>(_ values: repeat each T) { // Derleyici bu döngüyü derleme zamanında genişletir repeat print("Tür: \(type(of: each values)), Değer: \(each values)") } // Kullanım, aşağıdakine eşdeğer özel kod üretir: // describeValues(Int, String, Double) describeValues(42, "Swift", 3.14)
Ekibimiz, iOS için yüksek performanslı bir veri boru hattı çerçevesi tasarlıyordu; burada kullanıcıların heterojen dönüşüm adımlarını (örn. DecodeJSON<T>, Validate<U>, Map<V>) tek bir yürütme grafiğine bağlamaları gerekiyordu. API, farklı giriş ve çıkış türlerine sahip herhangi bir sayıda adımı kabul eden bir pipeline fonksiyonu gerektiriyordu ve veri akışının derleme zamanı bilgisinin korunması, optimizasyon geçişlerinin uygulanmasını sağladı.
Başlangıçta 1'den 6'ya kadar olan genel argümanlar için aşırı yükler uyguladık (örn. func pipeline<T1, T2>(_: T1, _: T2)). Bu, statik türleri koruyordu ve LLVM'nin zinciri inline etmesine izin veriyordu. Ancak, bu yaklaşım çok ayrıntılıydı ve sürdürülmesi zor olduğu için yüzlerce neredeyse aynı kod satırı gerektiriyordu. Kullanıcıları altı adıma sınırlandırıyordu ve her ek aritme, kod çoğaltma nedeniyle ikili boyutu katlanarak artırıyordu. Gereksinimler sekiz adımı desteklemek için değiştiğinde, yeniden yapılandırma çabası önemliydi.
Sonra, ilişkili türlerle AnyPipelineStep adında bir protokol tanımlamayı denedik; ardından [any AnyPipelineStep] parametre olarak kullanıldı. Bu sınırsız adımları destekliyordu ama her değer türünü (kodlanmış verileri taşıyan yapıları) yığın tahsis edilen varlık konteynerlerine itmek zorunda kalıyordu. Performans profil analizi, CPU zamanının %30'unun bu kutularda swift_retain ve swift_release işlemlerinde harcandığını ortaya koydu. Ayrıca, derleyici artık adım sınırları boyunca optimizasyon yapamıyordu çünkü ilişkili türler silinmişti ve her kavşakta dinamik dönüştürme gerekiyordu.
Swift 5.9 ile API'yi func pipeline<each Step: PipelineStep>(steps: repeat each Step) kullanacak şekilde yeniden yapılandırdık. Bu, derleyicinin kod tabanında karşılaştığı her belirli boru hattı bileşimi için benzersiz bir özel kod üretmesine olanak tanıyordu. Her adım kendi somut türünü korudu, bu da geçici veri yapılarına agresif inline ve yığın tahsis edilmesini sağladı. repeat anahtar kelimesi, derleme zamanında bitişik adımlar arasında tür uyumluluğunu doğrulamak için paketi yinelememize izin verdi.
Parametre paketlerini benimsedik çünkü performanstan ödün vermeden aritme kısıtlamasını ortadan kaldırdılar. Varlıklardan farklı olarak, paketler Swift'in optimizasyonu için genel imzayı korudular ve sıfır maliyetli soyutlamalar elde edildi. Yeniden yapılandırma, aşırı yükleme yaklaşımına göre çerçevenin ikili boyutunu %35 oranında azalttı ve varlık yaklaşımına göre ise verimliliği 4 kat artırdı. Geliştiriciler, her adımın belirli giriş/çıkış türleri için tam otomatik tamamlama desteği ile keyfi uzunlukta boru hatları oluşturabiliyor ve veri uyumsuzluklarını entegrasyon testi yerine derleme zamanında yakalayabiliyordu.
Adaylar sık sık paket kısıtlamalarının tek genel kısıtlamalar gibi davrandığını varsayıyor, ama Swift where ifadelerinde açık repeat kalıpları gerektirir. T paketinin her elemanını, farklı Item ilişkili türleri ile Container uyumlu olacak şekilde kısıtladığımızda, sözdizimi func process<each T: Container>(_ items: repeat each T) where repeat each T.Item: Equatable şeklinde olur. Derleyici, paketin her bir elemanı arasında genişleterek yapısal kısıtlama çözümlemesi yapar. Genel bir hatalı durum, tüm pakete tek bir ilişkili tür kısıtlaması uygulamaya çalışmaktır; bu başarısız olur çünkü her T.Item farklı bir türdür. Kısıtlamaların her bir eleman gereksinimlerinin bir kesişimini ürettiğini anlamak, çıkarım hatalarını giderirken önemlidir.
Geliştiriciler genellikle parametre paketlerinin her bağlamda sıfır maliyetli soyutlamaları garanti ettiğine inanıyorlar, ancak ABI sınırlarını aşarken veya opak sonuç türleri kullanıldığında, kutulama zorunlu olabilir. Özellikle, bir parametre paketi, başka bir dayanıklılık alanında bir fonksiyona geçirilen kaçırılmış bir kapanışta yakalandığında, Swift çalışma zamanı genel bir örnekleme üretmek zorunda kalabilir. Benzer şekilde, bir paket yinelemeyi içinden some Collection döndürmek, derleyicinin varlık konteynerini kullanmasını zorunlu kılar, çünkü somut dönüş türü her paket öğesiyle değişir. Bu, varlıkların satır içi tamponu (üç kelime) için yığın tahsisi ile bellek düzenini etkiler ve protokol tanık tablosu aracılığıyla dolayım ekler. Genişletmenin, çağrı noktasında paketin tamamının statik görünürlüğünü gerektirdiğini anlamak, performansın korunması açısından kritik öneme sahiptir.
Bu kısıtlama, struct Storage<each T> { repeat var item: each T } sözdiziminin her paket öğesi için ayrı saklanan özellikler olarak tanımlanacağını bekleyen adaylar için kafa karıştırıcıdır. Swift bu durumu yasaklar çünkü saklanan özelliklerin bellek yönetimi için değer tanık tablosunda bilinen sabit kaydırma ve offsetlere ihtiyacı vardır. Değişken sayıda özellikler, değişken boyutlu yapılar oluşturur ve genel türler için ABI kararlılık gereksinimlerini ihlal eder – değer tanık tablosu, örneklerin kopyalanması, taşınması ve yok edilmesi için statik bir düzen bekler. (repeat each T) biçiminde birleştirilme gerekliliği ile derleyici, paketi tüm elemanlarının kartezyen çarpımı ile elde edilen bir düzen ile tek bir bileşik değer olarak kabul eder. Bu, Storage'ın her özel versiyonunun önceden belirlenmiş bir ikili düzenle sahip olmasını sağlar ve çalışma zamanının, dinamik meta verilere başvurmadan uygun değer tanık fonksiyonlarını seçmesine olanak tanır. Geçici parametre paketleri (fonksiyon argümanları) ile kalıcı depolama (yapı alanları) arasındaki bu ayrımın anlaşılması, paketlerin kalıcı depolama için "dondurulması" gerekliliğini açıklığa kavuşturur.