Tarihçe: Rust'ın standart kütüphanesi Cow'ı (Clone-on-Write) tanıttığında, amacı hemen tahsisat zorunluluğu olmadan ödünç alınmış veya sahip olunan verilerle soyutlamak oldu. Clone trait'i başlangıçta düşünüldü, ancak sadece aynı türün özdeş bir kopyasını üretmeye izin verir. &str gibi ödünç alınmış verilerde, klonlama başka bir referans üretirken, değişkenlik için gereken sahip olunan String'i üretmez. ToOwned trait'i, ödünç alınmış ve sahip olunan formlar arasındaki ilişkiyi ifade etmek için özel olarak tasarlandı ve ilişkili Owned tipi ile birlikte gelir.
Problem: Eğer Cow Clone'a dayanıyorsa, Cow::Borrowed(&str)'yi değişiklik için sahip olunan bir temsile dönüştürmek, harici bir dönüştürme mantığı gerektirecektir. Clone, &str'yi String'e dönüştürmek için tür düzeyinde bir mekanizma sunmaz, bu da ya oluşturma zamanında gereksiz tahsislere ya da karmaşık manuel durum yönetimine yol açar. Bu, Cow'ın sıfır maliyetli soyutlama ilkesini ihlal eder, çünkü yığın tahsisatını, değişiklik gerçekten gerekli olana kadar ertelemeyi imkansız hale getirir.
Çözüm: ToOwned type Owned ve fn to_owned(&self) -> Self::Owned tanımlar, böylece &str, Owned = String belirtebilir. Bu, Cow::to_mut()'nin sadece değişiklik talep edildiğinde tembel şekilde tahsisat yapmasına olanak tanır. Eğer Cow zaten Owned ise, mevcut veriye tahsisat yapmadan değiştirilebilir bir referans döner. Aşağıdaki örnek bu verimliliği göstermektedir:
use std::borrow::Cow; fn normalize_whitespace(input: &str) -> Cow<'_, str> { if input.contains(" ") { let cleaned = input.replace(" ", " "); Cow::Owned(cleaned) // Yalnızca burada tahsisat yapar } else { Cow::Borrowed(input) // Sıfır maliyetli ödünç alma } }
Yüksek hacimli bir günlük işleme hizmeti, bellek haritalı dosyalardan kaynaklanan girişlerde zaman damgalarını normalize etmesi gerekiyordu. Giriş, haritaya işaret eden &str dilimleri olarak geldi, ancak yaklaşık %10'luk giriş değişiklikler için String tahsisi gerektirdi. İlk uygulama, String ve &str varyantları ile özelleştirilmiş bir enum kullandı ve her erişim noktasında kapsamlı kalıp eşleme ve hataya açık, ayrıntılı klonlama mantığı gerektirdi.
Alternatif 1: String'e hevesli dönüşüm. Ekip, tüm girişleri alındıktan hemen sonra String'e dönüştürmeyi düşündü. Bu yaklaşım, veri modelini sadeleştirdi ve yaşam süresi endişelerini ortadan kaldırdı, ancak ciddi bellek aşırılığına yol açtı. Zirve yükleri sırasında, değişiklik gerekmeyen %90'lık günlükler için bellek kullanımı iki katına çıktı ve 10GB'lık dosyaların işlenmesinde OOM hatalarına neden oldu.
Alternatif 2: copy-on-write ile Arc<str> kullanımı. Diğer bir seçenek, değişiklikler için Arc::make_mut ile birlikte değişmez paylaşımı sağlamak için Arc<str> kullanmaktı. Bu, paylaşımcı mülkiyet anlamları sağladı, ancak her erişim için atomik referans sayımı aşırı yükü getirdi. Ayrıca, paylaşılan olmaktan değiştirilebilir hale geçişi yönetmek için açık mantık gerektirdi ve ödünç alma modelini karmaşıklaştırdı, arzulanan ergonomiyi sunmazken.
Alternatif 3: Cow<'_, str> benimseme. Ekip, iki durumu soyutlamak için Cow seçti. Borrowed varyantları, tahsilat yapmadan bellek haritasına doğrudan işaret ederken; Owned varyantları, değiştirilmiş dizeleri tutuyordu. Bu çözüm, to_mut()'un tahsisi ilk değişiklik gerçekleşene kadar ertelemesi sayesinde Sıfır maliyet sunarken okumaya yönelik yollar için birleşik bir API sundu.
Sonuç: Parçalayıcı, yalnızca 200MB gerçek yığın tahsisi ile 10GB'lık günlük dosyalarını işleyerek yüksek verimliliği sürdürdü. Cow kullanarak sistem, manuel durum takibini ortadan kaldırdı, paralel işleme için Send ve Sync özelliklerini korudu ve özelleştirilmiş enum yaklaşımına kıyasla kod karmaşıklığını %60 oranında azalttı.
into_owned, yığın alanını tahsis etmek için derleme zamanı bilinen bir boyut gerektirir. Cow, Cow<'_, str> aracılığıyla boyutsuz türleri sarmalayabilir. Ancak Owned tipi (String) boyutludur. Adaylar genellikle Cow<'_, T> ile Cow<'_, &T>'yi karıştırır ve referans için değil, ödünç alınan tür için öznitelikler uygulamaya çalışır. ToOwned::Owned üzerine Sized kısıtlaması olmadan, derleyici into_owned için dönüş değerini oluşturamaz, çünkü bir boyutsuz str'yi doğrudan döndürmeye çalışır, oysa boyutlu String konteynerini döndürmelidir.
Cow, Borrow<Borrowed> uygulamasını gerçekleştirir ve Borrowed: ToOwned koşulunu sağlar, bu da Cow<String>'in &str ile bulunmasına olanak tanır. Ancak, Borrow katı bir sözleşme getirir: eğer iki değer Eq aracılığıyla eşitse, aynı hash değerlerini üretmelidir. Adaylar çoğunlukla Cow için özel PartialEq uygulaması (örneğin, büyük/küçük harf duyarsız karşılaştırma) yaparken, standart Hash uygulamasını korurlar. Bu, sözleşmeyi ihlal eder çünkü iki Cow değeri özel mantık altında eşit görünebilir, ancak Hash uygulaması orijinal baytları gördüğünde farklı hash değerlerine sahip olabilir. Bu, bir anahtarın görünüşte var olduğu ancak bulunamadığı durumda HashMap arama hatalarına yol açar.
Bir Borrowed varyantı oluşturmak için Cow, yaşam süresi 'a olan bir referansa &'a B ihtiyaç duyar. Kapsamlı bir Default uygulaması, 'static için geçerli bir referans üretmek gerekecektir (örneğin, '' için &'static str), ancak &str kendisi Default uygulamaz çünkü dönecek evrensel bir referans değeri yoktur. Adaylar genellikle Cow::Borrowed("")'ye varsayılan önerisinde bulunurlar, ancak bu ya 'static ömür kısıtlaması veya stabilize edilmemiş Rust'ta mevcut olmayan özel bir uygulama gerektirir. Sonuç olarak, standart kütüphane ToOwned::Owned: Default gerektirir ve bu, boş varsayılan değerler için bile Cow::Owned(String::new()) (bir tahsisat) gerektirir. Adaylar, belirli kapsamların içinde dize literallerinin mevcut olduğunu genel bir Default uygulaması ile karıştırdıkları için bu ayırt edici noktayı gözden kaçırabilirler.