Rust: Struct’ı Anlamak

2Nzd...aHc7
17 Jan 2024
26

Rust dilinde, “struct” adı verilen veri yapıları kullanılır. Bu veri yapıları, birden fazla ilişkili değeri içeren veri gruplarıdır. Veri yapılarının değişik tipte veriler içerebileceği gibi, tuple’ların da olduğu gibi, aynı tiplerdeki verileri de saklayabilir. Bununla birlikte, tuple’ların aksine, struct’ların içindeki verilere ait isimler belirtilir ve bu sayede verilerin ne anlama geldiği daha net anlaşılır. Ayrıca, struct’ların içindeki verilerin sıralamasına göre değil, isimlerine göre erişilebilmesi daha esnek bir yapı sağlar.
Bir struct tanımlamak için, “struct” anahtar kelimesi kullanılır ve struct’ın tamamı için bir isim verilir. Struct’ın ismi, bir arada tutulan verilerin anlamını açıkca ifade etmelidir. Ardından, bu verilerin isimleri ve tipleri virgülle ayrılarak, küçük ayraç içinde belirtilir. Örneğin, Listing 5–1'de bir kullanıcı hesabı hakkında bilgi saklayan bir struct gösterilmektedir.
5.1: User adında Struct örneği
Bir yapıyı tanımladıktan sonra kullanmak için, o yapının bir örneğini belirtilen alanların her biri için concret değerler belirterek oluştururuz. Yapının adını belirterek ve daha sonra alanların adlarını içeren anahtar: değer çiftlerini içeren köşeli ayraçlar ekleyerek bir örnek oluştururuz. Örnek oluştururken alanları tanımlandıkları sırada belirtmemiz gerekmez. Başka bir deyişle, yapı tanımı bir tür için genel bir şablon gibidir ve örnekler bu şablonu belirli verilerle doldurarak türün değerlerini oluşturur. Örneğin, Listing 5–2'de gösterildiği gibi bir kullanıcı tanımlayabiliriz.
5.2:User struct’ından bir instance oluşturma
Bir yapıdan belirli bir değeri almak için nokta (dot) notasyonunu kullanırız. Örneğin, bu kullanıcının e-posta adresine erişmek için user1.email kullanırız. Örnek değiştirilebilirse, belirli bir alana atama yaparak nokta notasyonunu kullanarak bir değeri değiştirebiliriz. Değiştirilebilir bir User örneğinin email alanındaki değeri nasıl değiştirileceğini gösteren Listing 5–3 aşağıdadır.
5.3:Bir Userörneğinin e-posta alanındaki değeri değiştirme
Tüm örneğin değiştirilebilir olması gerektiğini unutmayın; Rust, bazı alanların değiştirilebilir olmasına izin vermez. Herhangi bir ifade gibi, fonksiyon gövdesinin son ifadesi olarak yeni bir yapı örneği oluşturabiliriz ve bu yeni örnek sonuç olarak döndürülür.
Listing 5–4, verilen e-posta ve kullanıcı adı ile bir User örneği döndüren bir build_user fonksiyonunu gösterir. active alanının değeri true olarak, sign_in_count değeri de 1 olarak belirlenir.
5.4: E-posta ve kullanıcı adı alan bir build_user fonksiyonu
Fonksiyon parametrelerini yapı alanları ile aynı isimlere sahip olması mantıklıdır, ancak e-posta ve kullanıcı adı alan adlarını ve değişkenlerini tekrar etmek biraz yorucudur. Eğer yapı daha fazla alana sahip olsaydı, her adın tekrarlanması daha da rahatsız edici olurdu. Ne yazık ki, kullanışlı bir kısayol yoktur.

Alan Ön Tanımlık Kısayolunu Kullanma

Listing 5–4'de parametre adları ve yapı alan adları tamamen aynı olduğu için, build_user’ı tekrarlayan email ve username olmadan aynı şekilde davranan bir şekilde yeniden yazmak için alan ön tanımlık kısayol sözdizimini kullanabiliriz. Listing 5–5'de gösterildiği gibi:
5.5: email ve username parametrelerinin yapı alanlarıyla aynı isimlere sahip olduğu için alan ön tanımlık kısayolunu kullanan bir build_user fonksiyonu
Burada, email alanına sahip bir User yapısının yeni bir örneğini oluşturuyoruz. E-posta alanının değerini build_user fonksiyonunun email parametresinin değerine ayarlamak istiyoruz. E-posta alanı ve e-posta parametresinin aynı isim taşıdığı için, email: email yerine sadece email yazmak yeterlidir.

Diğer Örneklerden Yapı Örnekleri Oluşturma: Yapı Güncelleme Syntax’ı

Başka bir örnekten çoğunlukla aynı değerleri içeren, ancak bazı değişiklikler yapılmış yeni bir yapı örneği oluşturmak sıklıkla yararlıdır. Bu, yapı güncelleme sözdizimini kullanarak yapılabilir.
İlk olarak, Listing 5–6'da user2'yi normal bir şekilde güncelleme sözdizimini kullanmadan nasıl oluşturacağımızı gösteriyoruz. E-posta için yeni bir değer ayarlıyoruz, ancak Listing 5–2'de oluşturduğumuz user1 için aynı değerleri kullanıyoruz.
5.6: user1'den bir değeri kullanarak yeni bir User örneği oluşturma
Yapı güncelleme sözdizimini kullanarak, daha az kodla aynı etkiyi elde edebiliriz. Listing 5–7'de gösterildiği gibi. Sözdiziminde .. belirtilen diğer alanların açıkça ayarlanmayanlarının verilen örnekteki alanlarla aynı değere sahip olması gerektiğini belirtir.
5.7: User örneğine yeni bir e-posta değeri ayarlamak için yapı güncelleme sözdizimini kullanma ve user1'den kalan değerleri kullanma
Not: Yapı güncelleme sözdizimi, atama gibi = kullanır; bu, “Değişkenler ve Veri Etkileşimleri: Taşıma” bölümünde gördüğümüz gibi verileri taşıdığı içindir. Bu örnekte, user2'yi oluşturduktan sonra user1'i kullanamayız, çünkü user1'in username alanındaki String user2'ye taşındı. Eğer user2'nin hem email hem de username için yeni String değerleri verseydik ve bu nedenle sadece user1'den active ve sign_in_count değerlerini kullansaydık, user1 user2 oluşturulduktan sonra hala geçerli olurdu. active ve sign_in_count türleri Copy trait’ini uygulayan türlerdir, bu nedenle “Stack-Only Data: Copy” bölümünde tartıştığımız davranış uygulanır.

Rust, tuple’lara benzeyen tuple structs’ları da destekler

Tuple structs’lar, struct adı tarafından sağlanan ek anlamı içerir ancak alanlarıyla ilgili adları yoktur; sadece alanların türleridir. Tuple structs, tuple’ın tümüne bir ad vermek ve tuple’ı diğer tuple’lardan farklı bir türe dönüştürmek istediğiniz zaman ve regular bir struct’ta her alanı adlandırmak çok açık veya gereksiz olacağında yararlıdır.
Bir tuple struct tanımlamak için struct anahtar kelimesini ve struct adını takip eden tuple’daki türlerle başlatın. Örneğin, burada iki tuple struct olan Color ve Point’i tanımlıyor ve kullanıyoruz:
Not: black ve origin değerleri farklı türlerdir, çünkü farklı tuple struct örnekleridir. Tanımladığınız her struct kendi türüdür, ancak struct içindeki alanlar aynı türler olabilir. Örneğin, Color türünde bir parametre alan bir işlev, Point’i bir argüman olarak alamaz, ancak ikisi de üç i32 değerinden oluşan türlerdir. Aksi takdirde, tuple struct örnekleri tuple’ların benzemektedir, çünkü bunları bireysel parçalara ayırabilir ve bir . kullanarak bir bireysel değere erişebilirsiniz.

Hiçbir alanı olmayan structs’ları da tanımlayabilirsiniz!

Bunlar, “Tuple Type” bölümünde bahsettiğimiz () adı verilen birim türü ile benzer şekilde davranan birim benzeri structs’lar olarak adlandırılır. Birim benzeri structs’lar, bir türe bir trait uygulamak istediğinizde ancak kendine depolamak istediğiniz veri olmadığında yararlı olabilir. Trait’leri 10. Bölümde ele alacağız. Burada birim structs’ının tanımlandığı ve AlwaysEqual adında bir örneğinin oluşturulduğu bir örnek var:
AlwaysEqual’ı tanımlamak için struct anahtar kelimesini, istediğimiz adı, sonra bir noktalı virgül kullanıyoruz. Köşeli ayraçlar veya parantezler gerekmiyor! Daha sonra AlwaysEqual’ın bir örneğini subject değişkeninde benzer bir şekilde alabiliriz: tanımladığımız adı kullanarak, köşeli ayraçlar veya parantezler olmadan. Daha sonra bu tür için davranış tanımlamayı düşünün; her AlwaysEqual örneği herhangi bir diğer türün her örneğiyle her zaman eşit olacaktır, belki de test amaçlı bir sonuç elde etmek için. O davranışı uygulamak için veriye ihtiyaç duymayacaktız! 10. Bölümde, nasıl trait’ler tanımlayacağınızı ve bunları birim benzeri structs’lar dahil herhangi bir türe uygulayacağınızı göreceksiniz.

Struct Verilerinde OwnerShip

Listing 5–1'de User struct tanımında, &str dizi dilimi türü yerine sahip olduğumuz String türünü kullandık. Bu, structın her örneğinin tüm verilerini sahip olmasını ve bu verilerin tüm struct geçerli olduğu sürece geçerli olmasını istediğimiz için düşünülmüş bir seçimdir.
Structs’ların da başka bir şeye ait verilere referanslarını saklaması da mümkündür, ancak bunu yapmak için ömürlerini kullanmak gerekir, Chapter 10'ta tartışacağımız bir Rust özelliğidir. Ömürler, struct tarafından referans edilen verinin struct geçerli olduğu sürece geçerli olmasını sağlar. Örneğin, aşağıdaki gibi bir ömür belirtmeden bir referansı bir struct’ta depolamaya çalışırsanız bu işe yaramaz:
Derleyici ömür belirleyicilere ihtiyacı olduğunu belirterek hata verecektir:
10. Bölümde, bu hataları nasıl düzelteceğimizi ve structs’larda referansları depolayabileceğimizi tartışacağız, ancak şimdilik, String gibi sahip olduğumuz türleri &str gibi referanslar yerine kullanarak bu tür hataları düzelteceğiz.

Method Tanımlama

Rust dilinde, metotlar fonksiyonlara benzerdir: onları fn anahtar kelimesi ve bir isim ile bildiririz, parametreleri ve bir döndürme değeri olabilir ve bir metod başka bir yerden çağrıldığında çalıştırılacak kodu içerir. Fonksiyonlardan farklı olarak, metotlar bir struct (veya bir enum veya bir trait nesnesi) içerisinde tanımlanır ve ilk parametreleri her zaman self dir, bu da metotun çağrıldığı struct nesnesini temsil eder.
Alan fonksiyonunu değiştirin ve yerine bir Rectangle yapısına ait bir alan metodu tanımlayın, Listing 5-13'te gösterildiği gibi.
5–13: Rectangle yapısına ait bir alan metodunu tanımlama
Fonksiyonu Rectangle içerisinde tanımlamak için, Rectangle için bir impl (uygulama) bloğu başlatıyoruz. Bu impl bloğu içindeki her şey Rectangle tipi ile ilişkilendirilecektir. Daha sonra alan fonksiyonunu impl köşeli ayraçları içine taşıyor ve imza ve vücut içinde ilk (ve bu durumda tek) parametreyi self olarak değiştiriyoruz. Ana fonksiyonumuzda, alan fonksiyonunu çağırdık ve rect1'i bir argüman olarak geçirdik, yerine metot sözdizimini kullanarak Rectangle nesnemizin alan metodunu çağırabiliriz. Metot sözdizimi bir örnekten sonra gelir: bir nokta koyar ve metot adını, parantezlerini ve argümanları takip ederiz.
Alan metodunun imzasında, rectangle yerine &self kullanıyoruz: &Rectangle. &self aslında self: &Self için kısadır. impl bloğu içinde, Self türü, impl bloğu için olan türün bir takma adıdır. Metotların ilk parametresi olan Self türünde bir self adlı bir parametresi olmalıdır, bu nedenle Rust, bu kısaltmayı sadece ilk parametre yerinde self adı ile kısaltmanıza izin verir. Metodun burada olduğu gibi Self örneğini ödünç aldığını göstermek için & önünde self kısaltmasının hala gerekli olduğunu unutmayın. Metotlar, diğer parametreler gibi, Self'in sahip olabilir, Self'i değiştirilemez olarak ödünç alabilir veya Self'i değiştirilebilir olarak ödünç alabilir.
Burada &self için aynı nedeni kullandık: &Rectangle fonksiyon sürümünde kullandık: sahip olmak istemiyoruz ve yapı içindeki verileri sadece okumak istiyoruz, yazmak değil. Metodun bir parçası olarak çağrılan örneği değiştirmek istiyorsak, ilk parametre olarak &mut self kullanabiliriz. Örneğin, sahip olmayı seçtiğimiz bir metot nadirdir; bu teknik genellikle metot, self’i başka bir şeye dönüştürür ve orijinal örneğin dönüşümden sonra çağrıcının kullanımını önlemek isterseniz kullanılır.
Fonksiyonlardan metotları kullanmanın ana sebebi, metot sözdizimini sağlamak ve her metodun imzasında self türünü tekrarlamamaktır. Biz, bir türün bir örneğinin yapabileceklerini bir impl bloğu içine koyduk, böylece kodumuzun gelecekteki kullanıcıları kodumuzda Rectangle'ın yeteneklerini aramak zorunda kalmaz.
Bir metodun bir yapının alanlarından birine aynı ismi vermeyi seçebileceğimizi unutmayın. Örneğin, Rectangle üzerinde bir width adlı metot da tanımlayabiliriz:
Burada, width metodunu yapının width alanındaki değer sıfırdan büyükse true, sıfırsa false döndürecek şekilde seçiyoruz: aynı isimdeki bir metot içinde bir alanı istediğimiz amaçla kullanabiliriz. Ana fonksiyonumuzda, rect1.width parantezleri takip ettiğimizde, Rust’ın width metodunu anlama ihtimalini biliyor. Parantezleri kullanmadığımızda, Rust’ın width alanını anlama ihtimalini biliyor.
Sıklıkla, ancak her zaman olmayan, aynı isimdeki metotları verdiğimizde alana ait değeri döndürmek ve başka bir şey yapmamak istiyoruz. Bu tür metotlar getter olarak adlandırılır ve Rust, diğer diller gibi yapı alanları için otomatik olarak gerçekleştirmez. Getterler, alanı özel yapabilir ve metodu ise genel yaparak böylece türün genel API’si olarak o alana sadece okuma erişimine izin verirsiniz. Özel ve genel olmaya ve bir alanın veya metodun nasıl genel veya özel olarak adlandırılacağını 7. Bölümde tartışacağız.

“->” Operatörü Nerede?

C ve C++ dilinde, metotları çağırmak için iki farklı operatör kullanılır: nesneye direkt olarak bir metot çağırıyorsanız ve nesne için bir işaretçiye bir metot çağırıyorsanız -> işaretini kullanırsınız ve işaretçiyi ilk önce değerlendirmek zorundasınız. Başka bir deyişle, eğer nesne bir işaretçiyse, object->something() (*object).something()’e benzerdir.
Rust, -> operatörünün bir eşleniği yoktur; yerine, Rust’ın otomatik referans ve değerlendirme adı verilen bir özelliği vardır. Metotları çağırmak, Rust’ta bu davranışın görüldüğü az sayıda yerdir.
İşte nasıl çalışır: object.something() ile bir metotu çağırdığınızda, Rust otomatik olarak &, &mut veya * ekler ve object imzayla eşleşir. Başka bir deyişle, aşağıdakiler aynıdır:

İlk bakışta daha temiz görünüyor. Bu otomatik referanslama davranışı, metotların net bir alıcısı olmasından dolayı çalışır: self türü. Bir metodun alıcısı ve adı verildiğinde, Rust, metodun okuma (&self), mutasyona uğrama (&mut self) veya tüketimi (self) yapıp yapmadığını anlamaya çalışır. Rust’ın metod alıcıları için ödünç alma yapısının açık olduğu gerçeği, sahip olma pratikte ergonomik hale gelmesinin büyük bir parçasıdır.

Birden Fazla Parametreler ile Metodlar

Bir daha metot uygulayarak Rectangle yapısında pratik yapalım. Bu sefer, bir Rectangle örneğinin, içine tam olarak sığabileceğini döndüren bir Rectangle örneğini almasını istiyoruz. Yani, can_hold metodunu tanımladıktan sonra Listing 5-14'de gösterilen programı yazabilmek istiyoruz.
Rust’ta bir yöntem tanımlamak istiyoruz. Bu yüzden yöntemi impl Rectangle bloğu içinde tanımlayacağız. Yöntemin adı can_hold olacak ve parametre olarak başka bir dikdörtgenin değiştirilemeyen bir borç almasını sağlayacaktır. Parametre türünü yöntemi çağıran kod bize gösterebilir: rect1.can_hold (&rect2), &rect2 yöntemi çağıran kod bize gösterebilir: rect1.can_hold (&rect2), &rect2, bir Rectangle örneğinin değiştirilemeyen bir borçudur. Bu mantıklıdır, çünkü rect2'yi sadece okumak (yazmak için değil, bu da mutable bir borç gerektirecek anlamına gelecektir) ve can_hold yöntemini çağırdıktan sonra rect2'nin sahip olmayı sürdürmek istiyoruz, böylece tekrar kullanabiliriz. can_hold’un döndürülen değeri bir Boolean olacak ve uygulama, self’in genişliğinin ve yüksekliğinin, sırasıyla, diğer dikdörtgenin genişliği ve yüksekliğinden büyük olup olmadığını kontrol edecektir. Listing 5–13'ten aldığımız impl bloğuna yeni can_hold yöntemini ekleyelim, Listing 5–15'te gösterildiği gibi.
5–15: ‘can_hold’ metodunun bir başka dikdörtgen nesnesini parametre olarak alan bir ‘Rectangle’ sınıfında uygulanması
Bu kodu Listing 5–14'teki ana fonksiyon ile çalıştırdığımızda istediğimiz çıktıyı elde ederiz. Metodlar, self parametresinden sonraki imzaya birden fazla parametre ekleyebilir ve bu parametreler, işlevlerdeki parametreler gibi çalışır.

İlişkili Fonksiyonlar

İlişkili Fonksiyonlar impl bloğu içinde tanımlanan tüm fonksiyonlar, impl ile adlandırılan tip ile ilişkili olduğundan ilişkili fonksiyonlar olarak adlandırılır. self’i ilk parametre olarak almayan (ve böylece metod olmayan) ilişkili fonksiyonlar tanımlayabiliriz çünkü tipin bir örneğine ihtiyaç duymazlar. Zaten bir tane böyle bir fonksiyonu kullandık: String tipinde tanımlanan String :: from fonksiyonu.
Metod olmayan ilişkili fonksiyonlar, yapıcıların yaygın olarak kullanıldığı bir struct’ın yeni bir örneğini döndürecek olan yapıcılar için sıklıkla kullanılır. Bu yapıcılar genellikle ‘new’ olarak adlandırılır, ancak ‘new’ özel bir ad değildir ve dil içine yerleştirilmemiştir. Örneğin, bir ilişkili fonksiyon olan ‘square’ ile bir boyut parametresi sağlayarak dikdörtgeni kare yapmak için daha kolay bir yöntem sağlayabiliriz; bu sayede aynı değeri iki kez belirtme gereği ortadan kalkar.
Döndürme türünde ve fonksiyonun gövdesindeki Self anahtar kelimeleri, bu durumda Rectangle olan impl anahtar kelimesinden sonra görünen tipin takma adıdır.
Bu ilişkili fonksiyonu çağırmak için yapı adı ile :: sözdizimini kullanırız; Örneğin, let sq = Rectangle::square (3); bir örnektir. Bu fonksiyon yapı tarafından adlandırılır: :: sözdizimi ilişkili fonksiyonlar ve modüller tarafından oluşturulan ad alanları için kullanılır. Modüller hakkında 7. bölümde tartışacağız.

Çoklu impl Blokları

Her yapı için birden fazla ‘impl’ bloğuna izin verilir. Örneğin, Listing 5–15, her metodun kendi ‘impl’ bloğunda olduğu kodu gösteren Listing 5–16 ile aynıdır.
Listing 5–16: Birden fazla ‘impl’ bloğu kullanarak Listing 5–15'i yeniden yazma
Burada bu metodları birden fazla ‘impl’ bloğuna ayırmak için bir sebep yoktur, ancak bu geçerli bir sözdizimidir. Çoklu ‘impl’ bloğlarının kullanışlı olduğu bir durumu 10. bölümde, genel türler ve kalıplar hakkında tartıştığımızda göreceğiz.

Get fast shipping, movies & more with Amazon Prime

Start free trial

Enjoy this blog? Subscribe to Ates.eth

0 Comments