Ana içeriğe geç

ch20-04-advanced-types

Gelişmiş Tipler

Rust'un tip sistemi, şimdiye kadar bahsettiğimiz ancak henüz tartışmadığımız bazı özelliklere sahiptir. İlk olarak, yeni tiplerin (newtypes) neden faydalı olduğunu inceleyerek genel olarak yeni tiplerle başlayacağız. Ardından, yeni tiplerin benzeri ancak biraz farklı bir anlam taşıyan tür takma adlarına (type aliases) geçeceğiz. Ayrıca ! tipini ve dinamik boyutlu türleri de tartışacağız.

Tip Güvenliği ve Soyutlama için Yeni Tip Deseni Kullanımı

Not: Bu bölüm, “Yeni Tip Desenini Dışsal Türler Üzerinde Dışsal Özellikler Gerçekleştirmek için Kullanma” başlıklı önceki kısmı okuduğunuzu varsaymaktadır.

Yeni tip deseni, şimdiye kadar tartıştığımız görevlerin ötesinde, değerlerin hiç karıştırılmadığını statik olarak sağlamaya ve bir değerin birimlerini göstermeye de yararlıdır. Yeni tiplerin birimleri göstermek için nasıl kullanıldığını, 20-15 numaralı Listede gördünüz: Millimeters ve Meters yapılarının u32 değerlerini yeni tip içinde sarmaladığını hatırlayın.

tehlike

Bir Millimeters tipinde bir parametreye sahip bir fonksiyon yazarsak, yanlışlıkla bu fonksiyonu Meters tipinde bir değer ya da sıradan bir u32 ile çağıran bir program derlemeyecektir.

Yeni tip desenini, bir tipin bazı uygulama detaylarını soyutlamak için de kullanabiliriz: yeni tip, özel iç tipin API'sinden farklı bir kamu API'si sunabilir.

Yeni tipler ayrıca iç uygulamaları da gizleyebilir. Örneğin, bir kişinin kimliği ile adını ilişkilendiren bir HashMap'i sarmak için People türünü sağlayabiliriz. People kullanan bir kod, yalnızca sağladığımız kamu API'si ile etkileşimde bulunur; örneğin, People koleksiyonuna bir ad dizisi eklemek için bir yöntem; o kod, dahili olarak adlara bir i32 kimlik atadığımızı bilmek zorunda değildir. Yeni tip deseni, uygulama detaylarını gizlemek için hafif bir kapsülleme sağlamanın bir yoludur; bunu 18. bölümdeki “Uygulama Detaylarını Gizleyen Kapsülleme” bölümünde tartıştık.

Tip Takma Adları ile Tip Eşanlamlıları Oluşturma

Rust, var olan bir tipe başka bir isim vermek için tip takma adları tanımlama yeteneği sunar. Bunun için type anahtar kelimesini kullanırız. Örneğin, i32 için Kilometers takma adını şöyle oluşturabiliriz:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-04-kilometers-alias/src/main.rs:here}}

Artık Kilometers takma adı, i32 için bir eşanlamlıdır; 20-15 numaralı Listede oluşturduğumuz Millimeters ve Meters tiplerinden farklı olarak, Kilometers ayrı, yeni bir tip değildir. Kilometers tipine sahip değerler, i32 tipi değerler gibi işlenir:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-04-kilometers-alias/src/main.rs:there}}

Kilometers ve i32 aynı tip olduklarından, her iki tipin değerlerini toplayabiliriz ve i32 parametreleri alan fonksiyonlara Kilometers değerlerini geçebiliriz. Ancak, bu yöntemi kullanırken, daha önce tartıştığımız yeni tip deseninin sağladığı tip kontrolü yararını elde edemeyiz. Başka bir deyişle, bir yerde Kilometers ve i32 değerlerini karıştırırsak, derleyici bize hata vermez.

ipucu

Tip eşanlamlılarının ana kullanım durumu tekrarları azaltmaktır.

Örneğin, aşağıdaki gibi uzun bir tipimiz olduğunu varsayalım:

Box<dyn Fn() + Send + 'static>

Bu uzun tipin fonksiyon imzalarında ve kodun her yerinde tip açıklamaları olarak yazılması, yorucu ve hata yapmaya neden olabilir. Düşünün ki, böyle bir projede 20-24 numaralı Listede gösterilen gibi bir kod dolu.

{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-24/src/main.rs:here}}

Bir tip takma adı, bu kodu tekrarı azaltarak daha yönetilebilir hale getirir. 20-25 numaralı Listede, ayrıntılı tip için Thunk adında bir takma ad tanıttık ve türün tüm kullanımlarını daha kısa olan Thunk takma adıyla değiştirebiliriz.

{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-25/src/main.rs:here}}

Bu kod çok daha okunabilir ve yazılması kolay! Bir tip takma adına anlamlı bir isim seçmek, niyetinizi iletmeye yardımcı olabilir (thunk kelimesi, daha sonraki bir zamanda değerlendirilecek kod anlamına gelir, böylece saklanan bir closure için uygun bir isimdir).

Tip takma adları, tekrarları azaltmak için Result tipi ile yaygın olarak kullanılır. Standart kütüphanedeki std::io modülünü göz önünde bulundurun. G/Ç işlemleri genellikle işlemler başarısız olduğunda durumları ele almak için Result döndürür. Bu kitaplıkta tüm olası G/Ç hatalarını temsil eden bir std::io::Error yapısı bulunmaktadır. std::io içindeki birçok fonksiyon, E'sinin std::io::Error olduğu Result döndürecektir; örneğin Write trait'indeki bu fonksiyonlar:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-05-write-trait/src/lib.rs}}

Result çokça tekrar edilir. Bu nedenle, std::io bu tip takma ad deklarasyonuna sahiptir:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-06-result-alias/src/lib.rs:here}}

Bu deklarasyon std::io modülünde olduğundan, tam olarak nitelikli takma ad olan std::io::Result'yi kullanabiliriz; yani E, std::io::Error olarak doldurulmuş bir Result. Write trait fonksiyon imzaları şu şekilde görünmektedir:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-06-result-alias/src/lib.rs:there}}

Tip takma adı iki yönden faydalıdır: kodu yazmayı kolaylaştırır ve std::io'nun tamamında tutarlı bir arayüz sunar. Bir takma ad olduğu için, bu sadece başka bir Result'dir, yani Result üzerinde çalışan herhangi bir yöntemi onunla kullanabiliriz ve ? operatörü gibi özel söz dizimini de kullanabiliriz.

Asla Dönmeyen Never Tipi

Rust, tip teorisi dilinde boş tip olarak bilinen özel bir tip olan !'a sahiptir çünkü hiçbir değeri yoktur. Asla dönmeyecek bir fonksiyonun dönüş tipi yerine geçen asla tipi olarak adlandırmayı tercih ediyoruz. İşte bir örnek:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-07-never-type/src/lib.rs:here}}

Bu kod, "fonksiyon bar asla dönmez." şeklinde okunur. Asla dönen fonksiyonlara sapmaya neden olan fonksiyonlar denir. ! tipinde değerler oluşturamayacağımız için, bar hiçbir zaman geri dönemez.

not

Ama asla değer oluşturamayacağınız bir tipin ne işlevi var?

20-26 numaralı Listede yer alan sayı tahmin oyununun kodunu hatırlayın; burada bir kısmını tekrar oluşturduk.

{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-05/src/main.rs:ch19}}

O zaman, bu kodda bazı ayrıntıları atladık. 6. bölümde match Kontrol Akışı Operatörü” bölümünde, match kollarının hepsinin aynı türde dönüş yapması gerektiğini tartıştık. Yani, örneğin, aşağıdaki kod çalışmaz:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-08-match-arms-different-types/src/main.rs:here}}

Bu koddaki guess tipi bir tam sayı ve bir dize olmalıdır, Rust ise guess'in yalnızca bir tipe sahip olmasını gerektirir. Peki continue ne döndürür? 20-26 numaralı Listede, bir kola u32 değer döndürürken başka bir kolun continue ile bitmesine nasıl izin verdik?

Tahmin ettiğiniz gibi, continue bir ! değerine sahiptir. Yani, Rust guess tipini hesaplarken, ilk kol bir u32 değeri ve diğeri bir ! değeri ile bakar. ! hiçbir zaman bir değere sahip olamayacağı için, Rust guess tipinin u32 olduğunu kararlaştırır.

Bu davranışı tanımlamanın resmi yolu, ! tipindeki ifadelerin diğer herhangi bir türe zorlanabileceğidir. continue ile biten bu match kolunu sonlandırmamıza izin veriliyor çünkü continue bir değer döndürmez; bunun yerine kontrol akışını döngünün en üstüne geri döndürür, bu yüzden Err durumunda guess'e asla bir değer atamayız.

Asla tipi, panic! makrosuyla da kullanışlıdır. Option değerlerinde bir değer üretmek veya bu tanıma sahip panikleme fonksiyonunu çağırdığımızı hatırlayın:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-09-unwrap-definition/src/lib.rs:here}}

Bu kodda, 20-26 numaralı Listede bulunan match alanındaki şeylerin aynısı oldu: Rust, val'ın T tipinde ve panic!'in ! tipinde olduğunu gördü, yani genel match ifadesinin sonucu T olarak belirleniyor. Bu kod, panic! değer üretmediği için çalışır; programı sonlandırır. None durumunda, unwrap'ten bir değer döndürmeyeceğiz, bu nedenle bu kod geçerlidir.

! tipinde bir son ifade daha vardır: bir loop:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-10-loop-returns-never/src/main.rs:here}}

Burada, döngü asla bitmez, bu yüzden ! ifadenin değeridir. Ancak, break eklersek bu doğru olmaz; çünkü döngü breake geldiğinde sona erecektir.

Dinamik Boyutlu Türler ve Sized Trait'i

Rust, belirli türleri hakkında bazı ayrıntılara ihtiyaç duyar, örneğin, belirli bir tür için bir değer ayırmak için ne kadar alan gerektiği. Bu, tür sisteminin bir köşesini ilk başta biraz kafa karıştırıcı hale getirir: dinamik boyutlu türler kavramı. Bazen DST'ler veya boyutsuz türler olarak adlandırılan bu türler, yalnızca çalışma zamanında boyutlarını bilerek kod yazmamıza olanak tanır.

str adında bir dinamik boyutlu tipin ayrıntılarına girelim; bu tür, kitap boyunca kullandığımız bir türdür. Evet, &str değil, yalnızca str, bir DST'dir. String'in ne kadar uzun olduğunu çalışma zamanında bilmediğimizden, str tipinde bir değişken oluşturamayız veya str tipinde bir argümanı alamayız. Aşağıdaki kodu düşünün, bu çalışmaz:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-11-cant-create-str/src/main.rs:here}}

Rust'un, belirli bir tür için herhangi bir değer ayırmak üzere ne kadar bellek ayıracağını bilmesi gerekir ve bir türün tüm değerleri aynı miktarda bellek kullanmalıdır. Rust, bu kodu yazmamıza izin verirse, bu iki str değerinin aynı miktarda alan alması gerekir. Ama farklı uzunlukları vardır: s1 12 baytlık depolama gerektirirken, s2 15 bayt gerektirir. Bu nedenle, dinamik boyutlu bir tür tutan bir değişken oluşturmak mümkün değildir.

Peki ne yapacağız? Bu durumda, cevabı biliyorsunuz: s1 ve s2 tiplerini bir &str olarak yaparız. 4. bölümdeki “String Dilimleri” bölümünde, dilim veri yapısının yalnızca başlangıç pozisyonunu ve dilimin uzunluğunu depoladığını hatırlayın. Yani, &T bir değer olan T'nin bellek adresini depolarken, &str iki değerdir: str'nin adresi ve uzunluğu. Bu nedenle, bir &str değerinin boyutunu derleme zamanında biliyoruz: usize'nin iki katı uzunluğundadır. Yani, &str'nin boyutunu, referans ettiği string ne kadar uzun olursa olsun her zaman biliyoruz.

bilgi

Genel olarak, dinamik boyutlu türler Rust'ta bu şekilde kullanılır: dinamik bilginin boyutunu depolayan ekstra bir metadata sayısı vardır.

Dinamik boyutlu türlerin altın kuralı, dinamik boyutlu tür değerlerini her zaman bir tür belirteci (pointer) ardına koymamız gerektiğidir.

str'yi her türlü tür belirteci ile birleştirebiliriz: örneğin, Box veya Rc. Aslında, daha önce ama farklı bir dinamik boyutlu tipte: trait'lerle birlikte görmüştünüz. 18. bölümde “Farklı Türlerle Değerler İçin Trait Nesnelerini Kullanma” bölümünde, trait'leri trait nesneleri olarak kullanmak için, onları bir işaretçi (pointer) arkasına koymamız gerektiğini belirtmiştik; örneğin &dyn Trait veya Box (Rc de işe yarar).

DST'lerle çalışmak için, Rust, derleme zamanında bir türün boyutunun bilini olup olmadığını belirlemek için Sized trait'ini sağlar. Bu trait, derleme zamanında boyutu bilinen her şey için otomatik olarak uygulanır. Ayrıca, Rust, her genel fonksiyona otomatik olarak Sized üzerinde bir sınır ekler. Yani, aşağıdaki gibi bir genel fonksiyon tanımı:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-12-generic-fn-definition/src/lib.rs}}

aslında şöyle yazılmış gibi muamele görür:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-13-generic-implicit-sized-bound/src/lib.rs}}

Varsayılan olarak, genel fonksiyonlar derleme zamanında boyutları bilinen türler üzerinde yalnızca çalışır. Ancak, bu kısıtlamayı rahatlatmak için şu özel sözdizimini kullanabilirsiniz:

{{#rustdoc_include ../listings/ch20-advanced-features/no-listing-14-generic-maybe-sized/src/lib.rs}}

?Sized üzerindeki bir trait sınırı, "T boyutunu bilmek zorunda değildir" anlamına gelir ve bu notasyon, genel türlerin derleme zamanında bilinen bir boyuta sahip olma varsayımlarını geçersiz kılar. Bu anlamda ?Trait sözdizimi yalnızca Sized için mevcuttur, diğer herhangi bir trait için değil.

Ayrıca, t parametresinin tipini T'den &T'ye değiştirdiğimizi unutmayın. Tür Sized olmayabileceğinden, onu bir tür belirteci ardına kullanmamız gerekir. Bu durumda, bir referans seçtik.

İleri de fonksiyonlar ve closure'lar hakkında konuşacağız!