Rust Programlama Dili

Steve Klabnik ve Carol Nichols'tan, Rust Topluluğu'nun katkılarıyla...

Metnin bu sürümü Rust 2018 baskısının deyimlerinden yararlanabilmek için, tüm projelerin Cargo.toml belgelerinde edition = 2018 ifadesini içerdiğini ve Rust 1.41.0 veya daha üst bir sürümünü kullandığınızı varsayar. Rust'ı yüklemek veya güncellemek için kitabın Bölüm 1 "Kurulum" ayracını, sürümler hakkında bilgilere ulaşmak içinse Ek E belgesini takip edin.

Rust dili kitabının 2018 Sürümü, Rust'ı daha ergonomik ve öğrenilmesi kolay bir dil haline getiren iyileştirmeleri içerir. Kitabın bu baskısında aşağıdaki iyileştirmeleri yansıtan bir dizi değişiklik yer almaktadır:

  • "Büyüyen Projeleri Paketler, Sandıklar ve Modüller ile Yönetmek" başlıklı 7. Bölüm neredeyse yeniden yazıldı. 2018 Sürümü'ndeki modül sistemi ve yolların çalışma şekli daha tutarlı hale getirildi.
  • Bölüm 10'da ise, yeni impl Trait söz dizimini açıklayan "Parametre Olarak Özellikler" ve "Özellikleri Uygulayan Geri Dönüş Türleri" başlıklı yeni konulara yer verildi.
  • Bölüm 11'e, testlerin ? işlecini kullanarak nasıl yazılacağını gösteren, "Testlerde Result<T, E> Kullanımı" başlıklı yeni bir konu eklendi.
  • Derleyicide sağlanan iyileştirmeler yaşam süresi belirtimlerine daha az ihtiyaç duyduğundan, Bölüm 19'daki "Gelişmiş Yaşam Süreleri" bölümü kaldırıldı.
  • Daha önce Ek D belgesinde yer alan "Makrolar" konusu, prosedür makrolarını da içerecek şekilde genişletilerek, Bölüm 19'da yer alan "Makrolar" başlıklı müstakil alana taşındı.
  • Ek A'da yer alan "Anahtar Kelimeler" başlığında, 2015 ve 2018 Sürümü için yazılmış kodların birlikte çalışmasını sağlayan yeni "ham tanımlayıcılar" özelliğine yer verildi.
  • Ek D artık "Faydalı Geliştirme Araçları" olarak adlandırılıyor ve yakın zamanda yayınlanmış olan Rust kodu yazmanıza katkı sağlayacak araçlara yer veriyor.
  • Kitap boyunca bir dizi küçük hatayı ve kesin olmayan ifadeleri düzelttik. Bu sorunları bize bildiren okuyuculara teşekkür ederiz!

Rust Programlama Dili kitabının önceki sürümlerinde yer alan kodların, halihazırda kullandığınız Rust derleyicisinin sürümünü yükseltmiş olsanız bile, Cargo.toml belgelerinde edition = "2018" bildirimi olmadan da derlenmeye devam edeceğini hatırlatırız. İşte bu Rust’un geriye dönük uyumluluğunun bir garantisidir!

Bu kitabın çevrimiçi HTML formatı https://rustdili.github.io/ adresinde yer almaktadır. Kitabın çevrimdışı çalışan Orijinal dildeki kopyası ise, rustup ile gerçekleştirilen Rust kurulumuyla birlikte gelir. Bu kitabı okumak için terminalinizde rustup docs --book komutunu çalıştırmanız yeterli olacaktır.

Bu kitabı kapaksız ve e-kitap biçiminde No Starch Press adresinden temin edebilirsiniz.

Önsöz

Her zaman bu kadar net olmamakla beraber, Rust programlama dili temelde güç katmak ile ilgilidir: Şu an ne tür bir kodla uğraşıyor olursanız olun, Rust size daha ileri gitme, daha önce ulaştığınızdan çok daha geniş bir alan ve çeşitlilikte güvenli program yazma gücü ve olanaklarını sağlar.

Örneğin, bellek yönetimi, veri gösterimi ve eşzamanlılığın düşük düzeyli ayrıntılarıyla ilgilenen "sistem düzeyinde" çalışmayı ele alalım. Programlamanın bu alanı geleneksel olarak gizemli olarak görülmekte ve sadece yıllarını zorunlu olarak onun adı kötüye çıkmış tuzaklarından kaçınmayı öğrenmeye harcayan seçkin birkaç kişi tarafından erişilebilir durumdadır. Ve bu seçkin kişiler dahi kodlarını istismara, çökmeye yahut bozulmaya karşı korumak için dikkatli davranırlar.

Rust, bu tuzaklardan sıyrılıp kodlama serüveniniz boyunca size yardımcı olacak dost canlısı ve şık bir araç seti sağlayarak bu engelleri aşmanıza yardımcı olur. Daha düşük seviyeli kontrole dalması gereken programcılar, bunu Rust ile, geleneksel çökme veya güvenlik açıkları riskini üstlenmeden ve kararsız bir araç zincirinin detaylarını öğrenmek zorunda kalmadan yapabilirler. Daha da güzeli Rust sizin, hız ve bellek kullanımı açısından verimli ve güvenilir kodlara doğallıkla yönelmenizi sağlayacak şekilde tasarlanmıştır.

Halihazırda düşük seviyeli kodlarla çalışan programcılar, tutku ve heveslerini diri tutmak amacıyla Rust'ı kullanabilirler. Örneğin, Rust'ta paralel işlemler, derleyicinin klasik hataları kolaylıkla yakalamasından dolayı nispeten düşük ust lerinde bulacağınız CLI uygulamaları, web sunucuları ve başka pek çok kod çeşidinin yazılmasını keyifli hale getirecek kadar etkileyici ve ergonomiktir. Rust ile çalışmak, bir alandan diğerine geçiş yapabileceğiniz beceriler geliştirmenize olanak sağlarken; bir web uygulaması yazarak bu dili öğrenebilir, öğrendiklerinizi bir Raspberry Pi üzerinde kolaylıkla uygulayabilirsiniz.

Bu kitap tam olarak Rust kullanıcılarını güçlendirme potansiyeli taşırken, sadece Rust bilginizi değil, bir programcı olarak gelişim ve güveninizi de arttırmayı hedefleyen samimi ve ulaşılabilir bir metindir. Öğrenmeye hazırsanız buyrun başlayalım. Rust topluluğuna hoş geldiniz!

— Nicholas Matsakis ve Aaron Turon

Giriş

Not: Kitabın orijinal dilindeki sürümü basılı ve e-kitap biçiminde No Starch Press üzerinde ve orijinal dilinde sunulmaktadır.

Rust üzerine bir giriş kitabı olan Rust Programlama Dili'ne hoş geldiniz. Rust programlama dili, daha hızlı ve daha güvenilir programlar yazmanıza yardımcı olur. Programlama dili tasarımlarında üst düzey ergonomi ile düşük seviyeli kontrol, genel bir anlaşmazlık halindedir ve Rust bu çatışmaya meydan okur. Bu olağanüstü tasarım bir yandan bellek kullanımı gibi geleneksel olarak düşük seviyeli kontrol ile ilişkilendirilen tüm zorlukları ortadan kaldırırken, diğer yandan sağladığı güçlü teknik kapasite ve olağanüstü geliştirici deneyimini dengeleyerek bu ayrıntıları rahatlıkla kontrol etmenizi sağlar.

Rust Kimler İçin

Rust, çeşitli nedenlerden dolayı pek çok insan için idealdir. Bu insanların ait olduğu önemli üretim gruplardan birkaçına bakalım.

Geliştirici Ekipleri

Rust, sistem programlama bilgileri farklı düzeylerde olan kalabalık geliştirici ekipleri arasında işbirliğini tesis eden verimli bir araç olduğunu kanıtlıyor. Düşük seviyeli kod, pek çok dilde kapsamlı testler ve deneyimli geliştiriciler tarafından, kodun dikkatle incelenmesiyle yakalanabilen çözümü zor hatalara eğilimlidir. Rust derleyicisi, eşzamanlılık hataları dahil bu türden hatalı kodların derlenmesini reddederek adeta bir bekçi rolü oynar. Böylelikle derleyiciyi takımın bir üyesi olarak gören geliştirici ekibi, değerli zamanlarını hataları takip etmek yerine, programın mantığına odaklanarak geçirebilirler.

Rust ayrıca sistem programlama dünyasına çağdaş geliştirici araçları da sunar:

  • Rust ile birlikte gelen bağımlılık yöneticisi ve derleme aracı olan Cargo, Rust ekosisteminde bağımlılıkları ekleme, derleme ve yönetmeyi sancısız ve tutarlı hale getirir.
  • Rustfmt ise geliştiriciler arasında tutarlı bir kodlama tarzı oluşturur.
  • Rust Dil Sunucusu, kod tamamlama ve satır içi hata mesajları için Entegre Geliştirme Ortamı (IDE) entegrasyonunu destekler.

Bunlar ve Rust ekosistemindeki diğer araçları kullanan geliştiriciler sistem düzeyinde kod yazarken daha üretken olabilirler.

Öğrenciler

Rust, öğrenciler ve sistem kavramlarını öğrenmekle ilgilenenler için tasarlanmıştır. Pek çok kişi Rust kullanarak işletim sistemleri geliştirme gibi alanları öğrenmiştir. Rust topluluğu oldukça misafirperver olup öğrencilerin sorularını heves ve heyacanla yanıtlamaktan çekinmezler. Bu kitap gibi girişimler aracılığıyla Rust ekipleri, sistem konseptlerini mümkün olduğunca çok kişi için, özellikle de programlamaya yeni başlayanlar için erişebilir hale getirmek istiyorlar.

Şirketler

Büyüklü küçüklü yüzlerce şirket üretimlerinde çeşitli görevler için Rust'ı kullanıyorlar. Bu görevler arasında komut satırı araçları, web hizmetleri, DevOps araçları, gömülü cihazlar, ses, video analizi ve kod dönüştürme, kripto para birimleri, biyoinformatik, arama motorları, IOT uygulamaları, makine öğrenimi ve hatta Firefox web tarayıcısının önemli bölümleri bile bulunmakta.

Açık Kaynak Geliştiricileri

Rust, Rust programlama dili, topluluğu, geliştirici araçları ve kütüphanelerinin oluşumuna katkı sağlamak isteyen kişiler içindir. Rust diline katkıda bulunmanızı çok isteriz.

Hız ve İstikrara Değer Verenler

Rust, bir dilden hız ve istikrar bekleyenler içindir. Aslında hız demekle, hem Rust ile oluşturabileceğiniz programların hızını, hem de Rust'ın kodlama sürecinde sağladığı hızı kastediyoruz. Rust'ın derleyici kontrolleri, yeni özellik ekleme ve kodun yeniden düzenlenmesi aşamalarında kararlılık sağlaması onu, benzer denetimlerin olmadığı, geliştiricilerin genellikle değişiklik yapmaktan kaçındıkları programlama dillerinden ayırır. Rust'ın sıfır maliyetli soyutlamarı, elle yazılmış kodlar kadar hızlı çalışan düşük seviyeli kodlara derlenirken, daha fazla üst düzey işlevsellik arayışındaki Rust, güvenle çalışan kodu hızlı çalışan bir kod haline getirmeye çalışır.

Burada bahsedilen büyük ilgi gurupları dışında Rust, değişik konu ve geliştirme alanlarıyla alakalı pekçok kullanıcıya da destek olmayı umuyor. Sonuç olarak Rust'ın hedefi, geliştiricilerin onlarca yıldır verdiği ödünleri, güvenlik, üretkenlik, hız ve kullanılabilirlik sağlayarak ortadan kaldırmaktır. Rust'ın bu olanaklarını deneyerek sizin için yararlı olup olmayacağına karar verin.

Bu Kitap Kimler İçin

Bu kitap handisi olduğundan bağımsız halihazırda başka bir programlama dilinde kod yazdığınız varsayar. Kitabın içeriğini farklı programlama geçmişlerine sahip geniş bir izleyici kitlesi için erişilebilir kılmaya çalıştık. Kitapta programlamanın ne olduğu veya nasıl düşünülmesi gerektiği konusuna zaman ayırmadık. Eğer programlama konusunda yeniyseniz, işe programlamaya giriş konusunda yazılmış kitaplardan başlamanızı öneririz.

Bu Kitap Nasıl Kullanılır

Genel olarak bu kitabın baştan sona doğru sırayla okunması amaçlanmıştır. Sonraki bölümler, önceki bölümlerde işlenen kavramlar üzerine inşa edilmektedir. Genellikle önceki bölümlerde etraflıca incelenmeyen konuların ayrıntılarına daha sonraki bölümlerde değinilmektedir. Bu kitapta, kavramsal ve proje olarak ayrıştırılmış iki ayrı kısım bulunmaktadır. Rust hakkındaki bilinmesi gereken konular kavramsal kısımda işlenirken, öğrenilen konuların uygulamalarını proje kısmında gerçekleştireceğiz. Kitabın 2, 12 ve 20. bölümleri proje kısmını, diğer bölümler ise kavramsal kısmını oluşturmaktadır.

Bölüm 1, Rust'ın nasıl kurulacağını, bir "Merhaba Dünya" programının nasıl yazılacağını, Rust'ın paket yöneticisi ve yapım aracı olan Cargo'nun nasıl kurulacağını anlatır.

Bölüm 2, Rust diline uygulamalı giriş olarak tasarlandığından, bu bölümde işlenen yüksek düzeydeki kavramların ayrıntılarına sonraki bölümlerde değinilecektir. Kodlarla hemen haşır neşir olmak isteyenler için bu bölüm kol ve paçaların sıvanacağı yerdir.

İstiyorsanız şimdilik Rust'ın diğer programlama dillerindeki benzer özelliklerini tartıştığımız 3. Bölümü atlayarak, Rust'ın mülkiyet sistemini öğrenmek amacıyla doğrudan kitabın 4. Bölümüne atlamak isteyebilirsiniz. Bununla birlikte, bir sonraki bölüme geçmeden önce her bir özelliği etraflıca öğrenmeyi tercih eden titiz bir öğrenciyseniz, proje kısmı olan 2. Bölümü atlayarak doğrudan 3. Bölüme geçebilir, daha sonra etraflıca öğrendiklerinizi uygulamak üzere yeniden 2. Bölüme dönebilirsiniz.

Bölüm 5, yapılar ve bundan böyle metot olarak adlandıracağımız yapı işlevlerini, Bölüm 6 ise, numaralandırmalar (enums), örüntü eşleme ifadeleri (match expressions) ve if let kontrol akış yapılarını içerir. Rust'ta özel türlerinizi oluştururken yapılar ve numaralandırmalardan fazlasıyla yararlanacaksınız.

Bölüm 7 ise, kodunuz ve genel uygulama programlama arayüzünü (API) düzenleyebilmek için Rust'ın modül sistemi ve görünürlük kuralları hakkında bilgi verir.

Bölüm 8, vektörler, diziler ve eşleme haritaları (hash maps) gibi standart kütüphane tarafından sağlanan yaygın veri yapılarını anlatır.

Bölüm 9'da ise Rust'ın hata işleme felsefesini ve tekniklerini inceleyeceğiz.

Bölüm 10, farklı türlerin tek bir türmüş gibi davranabileği kodları yazmanıza olanak sağlayan genellenmiş veri türleri (generics), özellikler ve yaşam süreleri hakkında ayrıntılı bilgiler içerir.

Bölüm 11 ise, Rust'ın güvenlik garantilerine rağmen program mantığınızın doğru olup olmadığından emin olabilmeniz için gerekli olan testlerle ilgilidir.

Bölüm 12'de, metni dosyalarda arayan grep komut satırı aracından bir işlev alt kümesi oluştururken, önceki bölümlerde öğrendiğimiz kavramların çoğunu kullanacağız.

Bölüm 13, Rust'ın işlevsel programlama dillerinden esinlendiği özellikler olan kapamalar ve yineleyicilere odaklanıyor.

Bölüm 14'te, Cargo'yu derinlemesine inceleyecek, kendi kütüphanelerinizi başkalarıyla paylaşmanın en iyi yollarından bahsedeceğiz.

Bölüm 15, standart kütüphanenin sunduğu akıllı işaretçileri ve bu işaretçilerin işlevselliğini sağlayan özellikleri anlatır. ust ak kabul edilen belirli özellikleri ve bu özelliklerin deyimsel Rust'a nasıl dönüştürüldüğünü inceler.

Bölüm 18, Rust programlarında fikirleri ifade etmenin güçlü birer yolu olan örüntüler ve örüntü eşleştirme üzerine bir başvuru kaynağıdır. Liste gibi bir dizi ilginç ve gelişmiş konuları içerir.

Bölüm 20'de, eşzamanlı çoklu görevleri düşük seviyede bir program olarak çalıştıran web sunucusu projesini bitireceğiz.

Son olarak dil hakkında başvuru niteliğinde yararlı bilgiler içeren bazı ekler aşağıda listelenmektedir. Ek A, Rust'ın anahtar kelimelerini içerirç Ek B, Rust programlama dilinin işleç ve sembollerine yer verir. Ek C, Standart kütüphanenin sağladığı türetilebilir özellikleri kapsar. Ek D, Bazı faydalı geliştirme araçlarına atıfta bulunur. Ek E'de ise, Rust'ın sürümlerine yer verilmektedir.

Öğrenim sürenizce kitabın bazı bölümleri atlamak isterseniz, bunun kitabı yanlış okuduğunuz anlamına gelmediğini bilin ve bunu yapmaktan çekinmeyin. Herhangi bir güçlükle karşılaştığınızda önceki bölümlere dönmeniz gerekse bile size uygun olan öğrenim yolunu uygulamaktan çekinmeyin.

Rust öğrenme sürecinin önemli bir parçası, derleyicinin görüntülediği hata mesajlarının nasıl okunacağını öğrenmektir. Bu mesajlar sizi doğru koda yönlendireceğinden, pekçok hata senaryosunu içeren derlenmeyen örnekler vereceğiz. Rastgele bir örneği kopyalayıp çalıştırır ve bir hata alırsanız bunun hata gösterim örneği olup olmadığını anlamak için ilişkili metni okuduğunuzdan emin olun. Maskotumuz Ferris'i dikkatle takip ederseniz, çalışmaması gereken kodları kolayca anlayabilirsiniz.

Ferris Anlamı
Bu kod derlenmez!
Bu kod panik üretir!
Bu kod bloğu güvenli olmayan kod içerir.
Bu kod beklenen davranışı üretmiyor.

Çoğu durumda, hangi kod sürümünün çalışması gerektiğine dair sizi yönlendireceğiz.

Kaynak Kodu

Bu kitabın oluşmasını sağlayan kaynak kodlara Github üzerinden ulaşabilirsiniz.

Başlangıç

Öğrenilecek çok şey olmasına rağmen her yolculuğun bir başlangıç noktası vardır diyerek Rust yolculuğumuzu başlatalım. Bu bölümde aşağıdaki konuları tartışacağız:

  • Rust'ı Linux, macOS ve Windows işletim sistemlerine yüklemek.
  • Ekrana Merhaba Dünya yazan ilk Rust programımızı yazmak.
  • Rust'ın paket yöneticisi ve derleme sistemi olan Cargo'yu kullanmak.

Kurulum

İlk adımımız Rust'ı kurmak olacağından Rust sürümlerini ve bunlarla ilişkili araçları yönetmek için tasarlanmış bir komut satırı aracı olan rustup aracılığıyla Rust'ı indireceğiz. İndirme işlemini gerçekleştirebilmek için internet bağlantısına ihtiyacınız olacak.

Herhangi bir nedenle rustup aracını kullanmak istemiyorsanız diğer seçenekler için lütfen kurulum sayfasını inceleyiniz.

Aşağıdaki adımlar Rust derleyicisinin en son kararlı sürümünü yükleyecektir. Rust'ın kararlılık garantileri kitapta derlenen örneklerin, Rust'ın daha yeni sürümleriyle de derlenmeye devam etmesini sağlar. Rust genellikle hata mesaj ve uyarılarını iyileştirdiğinden, derleyici çıktıları sürümden sürüme biraz farklılık gösterebilir. Başka bir ifadeyle, buradaki kurulum adımlarına uyarak yüklediğiniz daha yeni ve kararlı Rust sürümleri, beklendiği gibi bu kitabın içeriğiyle uyumlu çalışacaktır.

Komut Satırı Gösterimi

Bu bölümde ve kitap boyunca, terminalde kullanılan bazı komutları göstereceğiz. Bir terminale girmeniz gereken satırların her biri $ karakteri ile başlar. Ancak bu karakter her komutun başlangıcını gösterdiğinden ayrıca elle yazılmasına gerek yoktur. $ karakteri ile başlamayan satırlar genellikle önceki komutun çıktısını gösterir. Buna ek olarak PowerShell'e özgü örneklerde $ yerine > karakteri kullanılır.

Linux veya macOS İçin rustup Kurulumu

Rust'ı Linux veya macOS bir sistemde kullanacaksanız bir terminal açarak aşağıdaki komutu giriniz:

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

Bu komut bir betik dosyasını indirerek Rust'ın en son kararlı sürümünü sisteminize yükleyecek olan rustup aracının kurulumunu başlatır. Kurulum esnasında sistem şifrenizi girmeniz istenebilir. Kurulumunuz başarılı olduğu takdirde işlem sonunda aşağıdaki satır görünecektir.

Rust is installed now. Great!

Ek olarak muhtemelen daha önceden sisteminize yüklenmiş olan bir tür bağlayıcıya da ihtiyacınız olacak. Eğer bir Rust programını derlemeye çalışırken bir bağlayıcının çalıştırılamadığını bildiren hatalar alıyorsanız bu, gerekli olan bağlayıcının sisteminizde yüklü olmadığını ve elle yüklemeniz gerektiği anlamına gelir. C derleyicileri genellikle doğru bağlayıcılarla birlikte gelir. C derleyicisinin kurulumunu öğrenmek için platformunuzun belgelerine göz atmanız gerekir. Ayrıca, bazı yaygın Rust paketleri C kodlarına bağımlı olduğundan bir C derleyicisine ihtiyaç duyacaktır. Bu nedenle şimdiden bir C derleyicisi edinmeniz yararlı olabilir.

Windows İçin rustup Kurulumu

Rust'ı Windows işletim sisteminize kurabilmeniz için Windows için yükle adresine giderek yükleme talimatlarını uygulamanız gerekir. Kurulumun bir aşamasında Visual Studio 2013 veya sonrası için C++ derleme araçlarına da ihtiyacınız olacağını bildiren bir mesaj alacaksınız. Derleme araçlarını edinmenin en kolay yolu Visual Studio 2019 için Derleme Araçları'nı yüklemektir. Bu yükleme esnasında yüklenecek bileşenleri seçmeniz istendiğinde "C++ Derleme Araçları"nı seçtiğinizden ve Windows 10 SDK ile ingilizce dil paketi bileşenlerinin dahil edildiğinden emin olun.

Bu kitabın geri kalanı, hem cmd.exe hem de PowerShell'de çalışan komutları kullanır. Bunların arasında belirgin farklılıklar olması durumunda hangisinin kullanılacağını size açıkça belirteceğiz.

Güncelleme ve Kaldırma

rustup aracılığıyla kurduğunuz Rust'ı en son sürümüne kolaylıkla güncelleyebilirsiniz. Bunun için terminalinizde aşağıdaki komut satırını çalıştırmanız yeterlidir:

$ rustup update

Eğer Rust ve rustup aracını kaldırmak isterseniz terminalinizde aşağıdaki satırı çalıştırmanız yeterlidir.

$ rustup self uninstall

Sorun Giderme

Rust'ın sisteminize doğru şekilde kurulup kurulmadığını kontrol etmek için terminalinizde aşağıdaki satırı çalıştırabilirsiniz:

$ rustc --version 

Terminalinizde Rust'ın son kararlı sürümün numarasını, kayıt değeri ve işlem tarihini aşağıdaki biçimde görmelisiniz:

rustc x.y.z (abcabcabc yyyy-mm-dd)

GGördüğünüz bilgiler bu biçimdeyse Rust'ı başarıyla yüklemişsiniz demektir. Eğer Windows kullanıyor ve bu çıktıyı göremiyorsanız Rust'ın %PATH% sistem değişkeninizde olup olmadığını kontrol etmelisiniz. Bunların her biri doğru uygulanmış, yerli yerindeyse ve Rust halen çalışmıyorsa yardım alacağınız birkaç yer var. Bunlardan en erişilebilir olanı Rust'ın Discord resmi kanalı olan #beginners kanalıdır. Orada size yardımcı olabilecek diğer Rustaceans'larla (evet kendimizi bu saçma isimle adlandırıyoruz) çevrimiçi sohbet edip sorununuza çözüm bulabilirsiniz. Diğer harika kaynaklar arasında ise Rust Kullanıcıları Forumu ile Stack Overflow bulunmaktadır.

Yerel Belgeler

Rust kurulumu, çevrim dışı okuyabilmeniz için Rust belgelerinin yerel bir kopyasını da içerir. Bu yerel belgeleri tarayıcınızda okuyabilmek için terminalinizde rustup doc komutunu çalıştırmanız yeterlidir.

Standart kütüphane tarafından sağlanan bir tür veya işlev hakkında bilgi almak ve nasıl kullanılacağını öğrenmek istiyorsanız uygulama programlama arabirimi (API) belgelerini inceleyebilirsiniz.

Merhaba, Dünya

Artık Rust'ı yüklediğimize göre ilk Rust programımızı yazabiliriz. Yeni bir programlama dilini öğrenme aşamasında Merhaba, dünya! çıktısını ekrana yazdırmak neredeyse gelenek haline geldiğinden biz de burada bu geleneğe uyacağız.

Not: Bu kitap komut satırı hakkında temel düzeyde bilgi sahibi olduğunuzu varsayar. Bununla birlikte Rust, kodlarınızı nasıl düzenleyeceğinize, hangi araçları kullanacağınıza veya onları nereye kaydedeceğinize karışmadığından, dilerseniz komut satırı yerine aşina olduğunuz entegre geliştirme ortamınızı (IDE) kullanabilirsiniz. Son zamanlarda Rust ekibi farklı IDE'ler ile entegrasyonu iyileştirmeye odaklandığından artık birçok IDE belli düzeylerde dil desteği sağlıyor. Tercih ettiğiniz IDE'nin yeterli dil desteği sağlayıp sağlamadığını IDE belgelerinden kontrol edebilirsiniz.

Bir Proje Dizini Oluşturmak

Öncelikle işe Rust kodlarımızı saklayacağımız bir proje dizini oluşturarak başlayalım. Rust için kodunuzu nerede sakladığınız önemli olmamakla beraber, bu kitapta yer alan alıştırma ve projeler için ana dizininizde (linux için Home)yeni bir projeler dizini oluşturup tüm çalışmalarınızı orada depolamanızı öneririz.

Ana dizinde "Merhaba, dünya" projesinin saklanacağı projeler dizinin oluşturabilmek için bir terminal penceresi açarak sırasıyla aşağıdaki komutları uygulayalım.

Linux, macOS ve Windows PowerShell için aşağıdaki komutları girin:

$ mkdir ~/projeler
$ cd ~/projeler
$ mkdir merhaba_dunya
$ cd merhaba_dunyamain.rsLE%\projeler"
> cd /d "%USERPROFILE%\projeler"
> mkdir merhaba_dunya
> cd merhaba_dunya

Bir Rust Programı Yazmak ve Çalıştırmak

Artık merhaba_dunya dizini içinde bulunduğumuza göre, programın kaynak kodunu kaydedeceğimiz ve main.rs olarak adlandıracağımız yeni bir dosya oluşturabiliriz. Rust'ta dosya adlarının birden fazla kelime içermesi durumunda alt çizgi ile ayrılması gerektiğini ve daima .rs uzantısıyla sonlandığını unutmayın. Dizin veya dosya isimlerinin bitişik olarak yazılması mümkünse de bu yaklaşımın pek önerilmediğini, merhabadunya.rs yerine merhaba_dunya.rs biçimindeki adlandırmaların tercih edildiğini aklınızda bulundurun.

Şimdi biraz önce oluşturduğumuz main.rs dosyasını açarak Örnek 1-1'de yer alan kod satırlarını dosyamıza ekleyelim:

Dosya adı: main.rs

fn main() {
    println!("Merhaba, dünya!");
}

Örnek 1-1: Ekrana "Merhaba, dünya!" yazdıran bir program.

Dosyayı kaydedip yeniden terminal penceresine dönelim. Programımızı Linux veya macOS üzerinde derleyip çalıştırabilmek için aşağıdaki komutları uygulayalım:

$ rustc main.rs
$ ./main
Merhaba, dünya!

Windows kullanıyorsanız ./main yerine ./main.exe komutunu kullanmanız gerekir:

> rustc main.rs
> .\main.exe
Merhaba, dünya!

Hangi işletim sistemini kullanıyor olursanız olun, bu komutları uyguladıktan sonra terminalinizde Merhaba, dünya! çıktısını görüyor olmalısınız. Bu çıktıyı görmüyorsanız, yardım için kurulum bölümündeki "Sorun Giderme" başlığına göz atın.

Eğer ekranınızda `Mmain.rs

Bir Rust Programının Anatomisi

Ekranımıza Merhaba, dünya! yazısını bastıran programda neler olup bittiğine daha yakından bakalım. Bulmacanın ilk parçası aşağıdadır:

fn main() {
#	println!("Merhaba, dünya!");	

}

Rust'ta bu satırlar bir işlevi tanımlar. Çalıştırılabilir tüm rust programlarında bulunan main işlevi, programın işletilen ilk kodu olması bakımından özel bir konumdadır. İlk satır parametre almayan ve hiçbir şey döndürmeyen işlev adını main olarak bildirir. Elimizde işleve iletilecek parametreler olsaydı onlar da yerlerini bu () parantezin içine konumlandırılmakla bulmuş olacaklardı.

Dikkat ederseniz işlev gövdesinin süslü parantezler {} içine alınmış olduğunu göreceksiniz. Rust'ta işlev gövdeleri bu süslü parantezler içine alınmak zorundadır. İşlev gövdesini saran ilk süslü parantezi, işlev bildirimiyle aynı satıra yerleştirirken arada bir boşluk bırakmak iyi bir kod yazım tekniğidir. Rust projelerinde standart kod yazım tekniğine bağlı kalmak ve kodlarınızı belirli bir şekilde biçimlendirmek için rustfmt adındaki otomatik biçimlendirme aracını kullanabilirsiniz. Bu araç Rust ekibi tarafından tıpkı rustc gibi standart Rust dağıtımına dahil edildiğinden halihazırda bilgisayarınızda kurulu durumda olmalıdır. Daha fazla ayrıntı için çevrimiçi belgelere başvurabilirsiniz.

Gövdesi süslü parantezler ile sarmalanmış olan main işlevinin içinde aşağıdaki kod satırı bulunur.

#fn main() {
    println!("Merhaba, dünya!");
#}

Bu küçük programdaki tüm işi üstlenerek metni ekrana yazdıran bu satırda dikkat edilmesi gereken dört önemli ayrıntı yer almaktadır.

İlki ve en az dikkat edileni, Rust stili girintilerde bir sekme (tab) yerine dört boşluk (space) kullanılır.

İkincisi, println! bir Rust makrosu çağırır. Eğer kodda bir işlev çağrısı yapılsaydı, println (! olmadan) yazılmış olacaktı. Rust makrolarını 19. bölümde ayrıntılarıyla inceleyeceğimizden şu an için ! işaretini gördüğünüz her yerde bunun bir işlev yerine, bir makroya yapılan çağrı olduğunu bilmeniz yeterlidir.

Üçüncüsü, "Merhaba, dünya!" olarak gördüğünüz dizgi, println! makrosuna argüman olarak geçirildiğinde ekrana yazdırılır.

Dördüncüsü, satırı noktalı virgül (;) ile bitiriyor olmamız ifadenin bittiğini ve bir sonrakinin başlayabileceğini bildirir. Rust kodlarındaki pek çok satır noktalı virgül ile sonlandırılır.

Derlemek ve Çalıştırmak Ayrı Birer Adımdır

Yeni oluşturulan bir programın çalıştırılma süreci adımlarını birlikte inceleyelim.

Rust programları çalıştırılmadan önce Rust derleyicisi tarafından ve rustc komutuna aşağıdaki gibi kaynak dosyası adı ileterek derlenmelidir:

$ rustc main.rs

Eğer C veya C++ dillerine aşina iseniz, bu işlemin gcc veya clang ile oldukça benzeştiğini fark edeceksiniz. Başarıyla gerçekleşen bir derlemenin ardından Rust çalıştırılabilir ikili (binary) bir dosya üretecektir.

Bu çalıştırılabilir dosyaya, Linux, macOS veya Windows PowerShell sistemlerinde, dizin içindeyken terminalinize ls komutu girerek ulaşabilirsiniz. Linux ve macOS sistemlerinde aynı dizinde iki adet dosya gösterilirken, Windows'ta PowerShell veya CMD ile üç tane dosya görüntülenecektir.

$ ls
main main.rs

Eğer Windows üzerinde CMD kullanıyorsanız aşağıdaki komutu girmeniz gereklidir:

> dir /B %= Buradaki /B seçeneği yalnızca dosya isimlerinin görüntülenmesini sağlar =%
main.exe
main.pdb
main.rs

Her iki durumda da .rs uzantılı bir kaynak kodu dosyası, (windows'ta main.exe olarak ancak diğer platformlarda sadece main olarak görünen) çalıştırılabilir ikili dosya, Windows için ek olarak hata ayıklama bilgilerini içeren .pdb uzantılı birer dosya gösterilecektir.

Bu dizinde çalıştırılabilir halde bulunan main ya da main.exe dosyasını aşağıdaki gibi kullanarak işletebilirsiniz:

$ ./main # ya da windows için .\main.exe

Eğer programınız "Merhaba, dünya!" metnini içeriyorsa bu komutu girdiğinizde terminalinizde "Merhaba, dünya!" yazdırılacaktır.

Programlama tecrübeniz Ruby, Python veya JavaScript gibi dinamik dillerden oluşuyorsa bu programın ayrı adımlar halinde derleyip çalıştırılmasına alışkın olmayabilirsiniz. Ancak Rust derlemeli bir dildir. Bu da bir Rust programının derlenmesiyle oluşturulan çalıştırılabilir dosyanın paylaşabilmesi ve Rust'ın kurulu olmadığı ortamlarda bile çalışması demektir. Oysa dinamik dillerden herhangi birinin kaynağını .py, .rb veya .js dosyası olarak biriyle paylaştığınızda bu dosyaların çalıştırılabilmesi, için o ortamda Python, Ruby ya da JavaScript uygulamalarının kurulu olması gerekmektedir. Bununla birlikte bu dillerde de programın çalıştırılması için yalnızca tek bir komut yeterlidir. Dil tasarımlarında bulunan her şey bir uzlaşma meselesinden başka bir şey değildir.

Programları rustc ile derlemek basit programlar için yeterli olmakla birlikte projeniz büyüyüp geliştikçe seçenekleri yönetmek ve kod paylaşımını kolaylaştırmanın yollarını arayacaksınız. Sonraki bölümde gerçek dünyada daha sık kullanılan ve daha karmaşık Rust programları yazmanıza yardım edecek olacak Cargo aracını keşfedeceğiz.

Merhaba, Cargo

Cargo Rust'ın derleme sistemi ve paket yöneticisidir. Bu araç sizin için; kod oluşturmak, kodun bağımlı olduğu kütüphaneleri (kodunuzun ihtiyaç duyduğu kitaplıklara bağımlılık adını veriyoruz) indirmek ve bunları derlemek gibi pek çok görevi yerine getirdiğinden çoğu Rustacean bu aracı Rust projelerini yönetmek için kullanır.

Daha önce yazdığımıza benzeyen basit Rust programlarının herhangi bir kütüphaneye bağımlılığı bulunmadığından "Merhaba dünya!" gibi basit bir projeyi Cargo ile derlemeye çalışsaydık, Cargo'nun sadece kodu derlemekle ilgili olan bölümü kullanılmış olacaktı. Daha karmaşık ve farklı kütüphanelerle çalışmaya ihtiyaç duyan Rust programları yazmaya niyetlendiğinizde, projenin Cargo aracılığıyla oluşturulması bağımlılıkları yönetmenize epey yardımcı olacaktır.

Rust projelerinin büyük çoğunluğu Cargo aracını kullandığından, bu kitabın geri kalan bölümlerinde sizin de bu aracı kullandığınız varsayılacaktır. Eğer "Kurulum" bölümünde açıklanan resmi yükleyicileri kullandıysanız Cargo aracı Rust ile birlikte gelmiş demektir. Rust'ı başka yollar ile kurduysanız sisteminizde Cargo'nun kurulu olup olmadığını terminalinize aşağıdaki kodları girerek kontrol edebilirsiniz.

$ cargo --version

Terminalinizde çıktı olarak bir sürüm numarası görünüyorsa Cargo aracınız Rust kurulumuyla birlikte yüklenmiş demektir. Eğer Command not found şeklinde bir hata ile karşılaşıyorsanız Cargo'nun nasıl kurulacağına dair kullandığınız kurulum yönteminin belgelerine bakmalısınız.

Cargo ile Proje Oluşturmak

Cargo aracılığıyla yeni bir proje oluşturarak bunun ilk projemiz olan "Merhaba dünya!" ile farklılıklarını incelemeye çalışalım. Hamgi işletim sizteminde olduğunuzdan bağımsız projeler dizini ya da kodlarınızı nereye kaydediyorsanız o dizine gelerek aşağıdaki komutları çalıştırım:

$ cargo new merhaba_cargo
$ cd merhaba_cargo

İlk komut "merhaba_cargo" adlı yeni bir dizin oluşturur. Cargo bir proje için gerekli olan dosyaları aynı zamanda projemizin de adı olan bu dizinde oluşturur.

Daha sonraki komutla da Cargo aracı tarafından yaratılan: Cargo.toml, main.rs adlı iki dosya ve src adlı bir dizinin listelendiği "merhaba_cargo" içeriği görüntülenir.

Bununla birlikte Cargo .gitignore dosyası ile eşliğinde yeni bir git deposunun da başlatılmasını sağlar. Eğer halihazırda mevcut bir deposu bulunuyorsa cargo new komutu yeni bir git deposu oluşturmaz. Eğer yine de bir git deposu oluşturmak istiyorsanız bunun için cargo new --vcs=git komutunu kullanmanız gerekir.

Not: Git yaygın olarak kullanılan bir sürüm kontrol sistemidir. Cargo'yu --vcs bayrağı aracılığıyla farklı bir sürüm kontrol sistemi kullanmak ya da sürüm kontrol sistemini kullanmamak üzere ayarlayabilirsiniz. Mevcut seçenekleri görmek için cargo new -help komutunu çalıştırmanız yeterlidir.

Cargo.toml dosyasını metin düzenleyicinizde açtığınızda içeriği örnek 1-2'dekine benzer biçimde görünmelidir.

Dosya adı: Cargo.toml

[package]
name = "merhaba_cargo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

[dependencies]

Örnek 1-2: cargo new komutuyla oluşturulan Cargo.toml dosyası içeriği

Bu dosya, Cargo'nun yapılandırma formatı olan TOML(Tom's Obvious, Minimal Language) biçimindedir.

İlk satırda bildirilen ve altındaki ifadeler tarafından oluşturulan [package] bölüm başlığı, paketin nasıl yapılandırıldığını gösterir. Paket içeriğine ayrıntı eklendikçe, farklı bölüm başlıkları da eklenecektir.

Sonraki dört satır ise programınızın Cargo tarafından derlenebilmesi için gereken: İsim, sürüm, programın yazarı ve kullanılacak Rust sürümü gibi yapılandırma bilgilerinden oluşur. Cargo isim ve e-posta gibi bilgileri ortamınzdan edineceğinden bilgileriniz eksik veya yanlış ise bu dosyayı şimdiden düzenleyerek kaydetmeniz gerekir. Sürüm anahtarı konusuna ise Ek E bölümünde değineceğiz.

Son satırda projenizin bağımlılıklarını listelemeye yarayan [dependencies] bölümü yer alır. Rust'ta kodların paketler halinde tutulduğu yapılara crate yani sandık adı verilir. Bu proje için harici bir sandığa ihtiyaç duymayacak fakat 2. Bölümde gerçekleştireceğimiz ilk projede bağımlılıklar bölümünü kullanacağız.

Şimdi src/main.rs dosyasını açalım ve inceleyelim:

Dosya adı: src/main.rs

fn main() {
    println!("Merhaba, dünya!");
}

Cargo sizin için tıpkı Örnek 1-1'de olduğu gibi ekranınıza "Merhaba, dünya!" metnini bastıran bir program oluşturdu. Önceki projemiz ile Cargo tarafından üretilen bu proje arasındaki farklar ise, Cargonun projeyi src adlı dizine yerleştirmesi ve üst dizinde ise bir Cargo.toml dosyası yaratması olarak özetlenebilir.

Cargo kaynak dosyalarının src dizininde bulundurulmasını bekler. Projenin ana dizin içeriği, sadece README dosyaları, lisans bilgileri, yapılandırma bilgileri ve kodunuzu ilgilendiren diğer şeyler içindir. Dolayısıyla Cargo, her şeyin kendi yerinde depolanmasını sağlayarak projelerinizin düzenlenmesine yardımcı olur.

Eğer bir projeyi ilk "Merhaba, dünya!" örneğindeki gibi Cargo kullanmadan başlattıysanız, bu projeyi Cargo kullanarak olşuturulmuş haline döndürebilirsiniz. Bunun için proje kodunu src dizinine taşımanız ve projenin ana dizinde Cargo.toml dosyası oluşturmanız yeterlidir.

Bir Cargo Projesini Derleyip Çalıştırmak

Şimdi "Merhaba, dünya!" programını Cargo kullanarak derleyip çalıştırdığımızda oluşan farklılıkları gözlemleyelim. Terminalinizde merhaba_cargo dizinine gelerek aşağıdaki komut yardımıyla projenizi oluşturun:

$ cargo build
    Compiling merhaba_cargo v0.1.0 (/home/rustdili/projeler/merhaba_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 1.54s

Bu komut geçerli dizininiz yerine target/debug/merhaba_cargo (veya Windows ortamında target\debug\merhaba_cargo.exe) konumunda çalıştırılabilir bir dosya oluşturu Bu dosyayı şu komutla çalıştırabilirsiniz:

$ ./target/debug/merhaba_cargo # veya Windows ortamında .\target\debug\merhaba_cargo.exe
Hello, world!

Her şey yolunda giderse terminalinizde "Hello, world!" yazısı görünecektir. Cargo uygulamasının ilk kez çalıştırılması projenizin ana dizininde Cargo.lock adında yeni bir dosya oluşturulmasına neden olur. Bu dosya projenizdeki bağımlılıkların tam sürümlerini takip eder. Halihazırda bu proje harici bir kasaya bağımlı olmadığından bu dosya epey boş görünecektir. Bu dosya içeriği Cargo tarafından otomatik olarak yönetildiğinden bunu elle değiştirmeniz gerekmez.

Aslında cargo build komutu ile derleyip ./target/debug/merhaba_cargo komutu ile çalıştırdığımız bu projeyi, cargo run komutu ile hem derleyip hem çalıştırabiliriz. Vereceğimiz tek bir cargo run komutu ile proje derlenecek ve ardından oluşturulan çalıştırılabilir dosya hemen işletilecektir.

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/merhaba_cargo`
Hello, world!

Bu kez Cargo'nun merhaba_cargo programını derlediğini bildiren çıktıyı göremediğimize dikkat edin. Bunun sebebi, Cargo'nun kaynak kodun değişmediğini bilmesidir. Eğer kaynak kodunu değiştirmiş olsaydınız program Cargo tarafından yeniden derlenerek çalıştırılacak ve terminalde aşağıdaki çıktı görünecekti:

$ cargo run
   Compiling merhaba_cargo v0.1.0 (/home/dogan/projeler/merhaba_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/merhaba_cargo`
Merhaba Cargo!

Yine Cargo tarafından sağlanan ve kodunuzun çalıştırılabilir olup olmadığını denetleyen, ancak çalıştırılabilir bir dosya oluşturmayan cargo check adında bir komut daha vardır:

$ cargo check
    Checking merhaba_cargo v0.1.0 (/home/dogan/projeler/merhaba_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.12s

Hangi durumlarda çalıştırılabilir dosya oluşturmanız gerekmez? Çoğu zaman cargo check, çalıştırılabilir dosya oluşturma adımını atladığı için cargo build işleminden daha hızlı olacaktır. Kodunuzu yazarken düzenli olarak çalışmanızın derlenip derlenmediğini kontrol etmek istiyorsanız cargo check komutunu kullanmanız yazım sürecinizi oldukça hızlandıracaktır. Bu sebepten birçok Rustacean, programın derlendiğindne emin olabilmek için, programlarını yazarken periyodik olarak cargo check ile çalıştırılabilirlik kontrolünü gerçekleştirir. En nihayetinde çalıştırılabilir dosyayı oluşturmaya hazır olduklarında cargo build komutunu uygularlar.

Cargo hakkında şimdiye kadar öğrendiklerimizi özetleyecek olursak:

  • Bir projeyi derlemek için cargo build komutunu kullanabiliriz.
  • Bir projeyi tek bir adımla derleyip çalıştırabilmek için cargo run komutunu kullanabiliriz.
  • Bir projenin hatalarını ikili kod üretmeden derleyerek kontrol edebilmek için cargo check komutundan yararlanabiliriz.
  • Cargo, derleme sonucunda oluşturulan çalıştırılabilir ikili dosyayı, kaynak koduyla aynı dizine koymak yerine target/debug dizinine kaydeder.

Cargo kullanmanın ek bir avantajı da, hangi işletim sisteminde çalışıyor olursanız olun, kullanacağınız komutların aynı olmasıdır. O nedenle bundan sonra Linux, macOS veya Windows işletim sistemlerinin her biri içn ayrı ayrı özel talimatlar sağlamayacağız.

Sürüm Amaçlı Derleme

Pisayasa sürülmeye hazır hale gelen projenizi en iyileştirme olanakları ile derleyebilmek için cargo build --release komutundan yararlanabilirsiniz. Bu komut çalıştırılabilir dosyanızı target/debug dizini yerine target/release dizinine kaydedecektir. Fakat en iyileştirmeler, Rust kodlarının daha hızlı çalışmalarını sağlamakla birlikte, programın derlenmesi için gereken süreyi uzatır. O nedenle en iyileştirme olanaklarında: İlki, hızlı ve sık derleme işlemleri için kullanılan geliştirme profili, diğeriyse tekrar tekrar derlenmeyecek ve çalışır halini olabilecek en kısa sürede kullanıcıya teslim edebileceğiniz nihai programı oluşturan olmak üzere iki farklı profil sunulur.

Eğer kodunuzun çalışma süresini ölçmek istiyorsanız, target/release dizininde bulunan çalıştırılabilir dosyayı cargo build --release komutu ile test ettiğinizden emin olun.

Konvansiyonel Cargo

Basit projeler için Cargo rustc kullanımından daha kullanışı değilse de programlarınız karmaşıklaştıkça değerini kanıtlayacaktır. Birden fazla sandıktan oluşan karmaşık projelerde yapı koordinasyonu Cargo'ya devretmek oldukça faydalıdır.

Her ne kadar merhaba_cargo projesi basit bir projeymiş gibi görünüyor olsa da Rust kariyeriniz boyunca karşılaşacağınız birçok aleti kullanmanızı sağlıyor. Bununla birlikte mevcut herhangi bir Rust projesi üzerinde çalışabilmeniz için, Git üzerinden arzu ettiğiniz kodu indirmek, ardından projenin bulunduğu dizinde kodu derleyecek olan şu komutları girmeniz yeterlidir:

$ git clone herhangibirurl.com/herhangibirproje
$ cd herhangibirproje
$ cargo build

Cargo hakkında daha fazla bilgi edinmek istiyorsanız kendi belgelerine bakmanız yeterlidir.

Özet

Oldukça iyi başlayan Rust yolculuğunuzda aşağıdakileri öğrendiniz:

  • Rust'ı, rustup kullanarak en son kararlı sürümüne yüklemek.
  • Rust'ı daha yeni bir sürümüne yükseltmek.
  • Yerel olarak yüklenen belgelere erişmek.
  • Bir "Merhaba, dünya!" programını yazarak doğrudan rustc kullanarak çalıştırmak.
  • Yeni bir projeyi Cargo komut ve kurallarını kullanarak derleyip çalıştırmak.

Şimdi Rust kodu okuyup yazma becerilerimizi geliştirebilmek için daha sağlam bir program yazmamız gerekiyor. Bu nedenle 2. Bölümde bir tahmin oyunu programı yazacak ve inceleyeceğiz. Eğer öğrenme sürecinize "Ortak Programlama Kavramları"nın nasıl çalıştığını öğrenerek devam etmek istiyorsanız 3. Bölüme ilerleyebilir, ardından 2. Bölüme geri dönebilirsiniz.

Bir Tahmin Oyunu Programlamak

Ortak Programlama Kavramları

Değişkenler ve Değişkenlik

Veri Türleri

İşlevler

Yorumlar

Kontrol Akışı

Mülkiyeti Anlamak

Mülkiyet kavramı, Rust'ın bellek garantilerini bir çöp toplayıcı mekanizmasına gerek duymadan vermesini sağlayan benzersiz bir özelliktir. Bu nedenle, Rust' ta mülkiyetin nasıl çalıştığını anlamak oldukça önemlidir. Bu bölümde mülkiyetin yanı sıra bu kavramla ilişkili; borçlanma, dilimler ve Rust'ın verileri belleğe nasıl yerleştirdiğinden bahsedeceğiz.

Mülkiyet Nedir?

Referanslar ve Borçlanma

Dilim Türü

İlişkili Verileri Yapılandırmak için Yapıları Kullanmak

Yapıları Tanımlamak ve Örneklemek

Yapıları Kullanan Örnek Bir Program

Metod Sözdizimi

Numaralandırmalar (Enums) ve Örüntü Eşleme (Pattern Matching)

Bir Sıralama Tanımlamak

Kontrol Akışı İşleci match

if let ile Kısa Kontrol Akışı

Büyüyen Projeleri Paketler, Sandıklar ve Modüller ile Yönetmek

Paketler ve Sandıklar

Kapsam ve Gizlilik Kontrolü İçin Modül Tanımlamak

Modül Ağacındaki Bir Öğeye Başvurmanın Yolları

use Anahtar Kelimesi ile Yolları Kapsama Getirmek

Modülleri Farklı Dosyalara Ayırmak

Ortak Koleksiyonlar

Değer Listelerini Vektör Kullanarak Depolamak

Dizgelerle UTF-8 Kodlu Metinleri Saklamak

İlişkili Değerlere Sahip Anahtarları Eşleme Haritalarında Saklamak

Hata Yönetimi

panic! ile Kurtarılamayan Hatalar

Result ile Kurtarılabilir Hatalar

panic! Olmak ya da panic! Olmamak

Genellenmiş Türler, Özellikler ve Yaşam Süreleri

Genellenmiş Veri Türleri

Özellikler: Paylaşılan Davranışı Tanımlamak

Referansları Yaşam Süreleri ile Doğrulamak

Otomatik Testler Yazmak

Testler Nasıl Yazılır?

Testlerin Nasıl Çalıştırılacağını Denetlemek

Test Organizasyonu

Bir I/O Projesi: Komut Satırı Programı Oluşturmak

Komut Satırı Argümanlarını Kabul Etmek

Bir Dosyayı Okumak

Modülerlik ve Hata Yönetimini Geliştirmek İçin Yeniden Düzenlemek

Test Odaklı Geliştirme ile Kütüphane İşlevselliğini Artırmak

Ortam Değişkenleriyle Çalışmak

Hata Mesajlarını Standart Çıktı Yerine Standart Hataya Yazmak

İşlevsel Dil Özellikleri: Yineleyiciler ve Kapamalar

Rust'un tasarımında mevcut birçok dil ve teknikten esinlenilmesinin dile en önemli katkısı işlevsel programlama olmuştur. İşlevsel programlamanın temelindeyse; işlevleri bağımsız değişkenlere geçirirerek değer olarak kullanmak, başka işlevlerden değer döndürmek, elde edilen değerleri daha sonra kullanmak üzere değişkenlere atamak gibi işlemler bulunur.

Bu bölümde, işlevsel programlamanın ne olup olmadığını tartışmak yerine Rust'ın, işlevsel olarak adlandırılan birçok dildeki özelliklere benzer bazı özelliklerini tartışacağız.

Dahası özellikle şu konuları ele alacağız:

  • Kapamalar, bir değişkene depolanabilen işlev benzeri yapılar
  • Yineleyiciler, dizi öğelerini seri olarak işlemenin yolları
  • Bu iki özeliğin, Bölüm 12'deki I/O projesini geliştirmek için kullanılması
  • Ve bu özelliklerin performansı (Spoiler uyarısı: düşündüğünüzden daha hızlı!)

Diğer bölümlerde ele aldığımız sıralamalar ve örüntü eşleme gibi diğer Rust özellikleri de işlevsel programlama tekniklerinden etkilenirler. Kapamalar ve yineleyicilerde uzmanlaşmak, hızlı ve deyimsel Rust kodları üretmenin önemli bir parçası olduğundan bu bölümün tamamını bu konulara ayıracağız.

Kapamalar: Ortam Değişkenlerini Yakalayabilen İsimsiz İşlevler

Rust'un kapamaları, bir değişkene kaydedebileceğiniz veya diğer işlevlere argüman olarak iletebileceğiniz isimsiz işlevlerdir. Kapamaları tek bir yerde oluşturabilir ve daha sonra farklı bir bağlamda değerlendirmek için yeniden çağırabilirsiniz. İşlevlerin aksine kapamalar, kullanacakları değerleri tanımlandıkları kapsamdan elde edebilirler. Kapamaların sahip olduğu bu özelliklerin, kodların yeniden kullanımına ve davranışlarının özelleştirilmesine nasıl izin verdiğini göstereceğiz.

Kapamalar ile bir davranışı soyutlamak

Bir kapamayı daha sonra işletilmek üzere saklamanın yararlı olduğu bir örnek üzerinden ilerleyerek, kapama söz dizimi, tür çıkarımı ve özelliklerinden bahsedelim.

Özel egzersiz planları üreten bir uygulama projesinde çalıştığımızı varsayalım. Egzersiz planlarını oluştururken kullanıcısının belirttiği; yaş, vücut kitle indeksi, egzersiz tercihleri, son egzersizler gibi birçok faktörü dikkate alan bu algoritmanın arka ucunu da Rust ile yazdığımızı düşünelim. Bu örnekte algoritmanın kullanılabilir olmasından ziyade birkaç saniye gibi kısa bir sürede çalışması beklendiğinden, algoritmayı bir kez ve sadece ihtiyacımız olduğunda çağararak kullanıcıyı boş yere bekletmek istemiyoruz.

Bu varsayımsal algoritmayı örnek 13-1' de yer alan simulated_expensive_calculation işlevini çağırarak simüle edecek, ekrana "yavaşça hesaplanıyor..." yazdırdıktan sonra iki saniye beklecek, ardından işleve ilettiğimiz sayıyı geriye döndüreceğiz:

Dosya adı: src/main.rs

use std::thread;
use std::time::Duration; 

fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("yavaşça hesaplanıyor...");
    thread::sleep(Duration::from_secs(2));
    intensity
}

#fn main() { 
#    simulated_expensive_calculation(10);
#}

Örnek 13-1: İki saniyelik ayakta durma egzersizinde kullanılan ve çalışması iki saniye süren simulated_expensive_calculation işlevi

Bu programın bir sonraki önemli adımı ise, egzersiz uygulamasının bölümlerini içeren main işlevidir. Bu işlevdeyse kullanıcı bir egzersiz planı istediğinde uygulamanın çağıracağı kod yer alır. Çünkü uygulamanın ön ucuyla etkileşim, kapamaların kullanımıyla ilgili olmadığından, programımıza girdileri temsil eden değerleri kodlayacak ve ardından çıktıları yazdıracağız.

İhtiyacımız olan girdiler şunlardır:

  • Kullanıcının talep ettiği egzersizin düşük ya da yüksek yoğunluklu olduğunu gösteren bir egzersiz yoğunluk numarası
  • Farklı antreman planlarının üretilmesini sağlayan rastgele bir sayı

Çıktımız ise önerilen egzersiz planı olacaktır. Örnek 13-2 bu verilerin main işlevinde nasıl kullanıldığı gösterilir:

Dosya adı: src/main.rs

#use std::thread;
#use std::time::Duration; 
#
#fn simulated_expensive_calculation(intensity: u32) -> u32 {
#    println!("yavaşça hesaplanıyor...");
#    thread::sleep(Duration::from_secs(2));
#    intensity
#}
#
#fn generate_workout(intensity: u32, random_number: u32) { 
#    if intensity < 25 { 
#        println!( 
#            "Bugün, {} şınav çek!", 
#            simulated_expensive_calculation(intensity) 
#        ); 
#        println!( 
#            "Sonrasında {} mekik çek!", 
#            simulated_expensive_calculation(intensity) 
#        ); 
#    } else { 
#        if random_number == 3 { 
#            println!("Bugün mola ver! Sıvı tüketmeyi de ihmal etme!"); 
#        } else { 
#            println!( 
#                "Bugün, {} dakika koş!", 
#                simulated_expensive_calculation(intensity) 
#            ); 
#        } 
#    }
#}

fn main() { 
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;
    
    generate_workout(
        simulated_user_specified_value,
        simulated_random_number
    );
}

Örnek 13-2: Kullanıcı girişi ve rastgele sayı oluşturmayı simüle etmek için kodlanmış değerlerden oluşan main işlevi

Sadeliği koruyabilmek amacıyla simulated_user_specified_value değişkenini 10, simulated_random_number değişkenini 7 değerleriyle sabit biçimde kodladık. Oysa gerçek bir programda, yoğunluk numarasını uygulamanın ön ucundan alır ve 2. Bölüm' deki tahmin oyunu örneğinde yaptığımız gibi rand sandığını rastgele bir sayı üretmek için kullanmaya çalışırdık. Örneğimizdeki main işlevi simüle giriş değerlerini kullanarak create_workout işlevini çağırmaktadır.

Artık ihtiyaç duyduğumuz içeriği elde ettiğimize göre, algoritma üzerine yoğunlaşabiliriz. Örnek 13-3' te yer alan generate_workout işlevi uygulamanın çalışma mantığını yansıtan en önemli kısmı olduğundan, örnek üzerinde çalıştığımız sürece gerçekleştireceğimiz bütün kod değişiklikleri yalnızca bu işlevi kapsayacaktır:

Dosya adı: src/main.rs

#use std::thread;
#use std::time::Duration; 
#
#fn simulated_expensive_calculation(intensity: u32) -> u32 {
#    println!("yavaşça hesaplanıyor...");
#    thread::sleep(Duration::from_secs(2));
#    intensity
#}
#
fn generate_workout(intensity: u32, random_number: u32) { 
    if intensity < 25 { 
        println!( 
            "Bugün, {} şınav çek!", 
            simulated_expensive_calculation(intensity) 
        ); 
        println!( 
            "Sonrasında {} mekik çek!", 
            simulated_expensive_calculation(intensity) 
        ); 
    } else { 
        if random_number == 3 { 
            println!("Bugün mola ver! Sıvı tüketmeyi de ihmal etme!"); 
        } else { 
            println!( 
                "Bugün, {} dakika koş!", 
                simulated_expensive_calculation(intensity) 
            ); 
        } 
    }
}
#
#fn main() { 
#    let simulated_user_specified_value = 10;
#    let simulated_random_number = 7;
#    
#    generate_workout(
#        simulated_user_specified_value,
#        simulated_random_number
#    );
#}

Örnek 13-3: Girdi ve çağrılarına göre egzersiz planları yazdıran simulated_expensive_calculation işlevi

Örnek13-3'teki kod, yavaş hesaplama yapan işleve çok sayıda başvuruda bulunur. İlk if bloğu simulated_expensive_calculation işlevini iki defa çağırırken, else bloğunun içindeki birinci if bloğu ise başvuruda bulunmaz. Oysa bir sonraki else bloğunda simulated_expensive_calculation işlevine yeniden çağrıda bulunulur.

Öncelikle generate_workout işlevinin beklenen davranışı, kullanıcının düşük yoğunluklu bir egzersizi mi (25'ten az bir sayı ile gösterilir) yoksa yüksek yoğunluklu bir egzersizi mi (25 veya daha büyük bir sayı) isteyip istemediğini kontrol etmektir.

Düşük yoğunluklu egzersiz planları, simüle ettiğimiz karmaşık algoritmaya dayanan bir dizi şınav ve mekik antremanını önerecektir.

Kullanıcının yüksek yoğunluklu bir egzersiz planı istemesi halinde; işin içine biraz daha mantık girecek ve: Uygulamanın oluşturduğu rastgele sayı 3 olduğunda kullanıcıya mola vererek sıvı tüketmesi önerilecek, diğer hallerdeyse algoritmanın belirlediği süre kadar koşması önerilecektir.

Bu kod, şimdilik işverenimizin istediği şekilde çalışmaktadır. Ancak bir süre sonra, şirketimizin veri bilimi ekibinin, simulated_expensive_calculation işlevini çağırma yönteminde bir takım değişiklikler yapılması gerektiğine karar verdiğini varsayalım. Bu güncelleme senaryosunda değişikliklerin basit tutulabilmesi için simulated_expensive_calculation işlevini kendisine başka bir çağrı eklemeden, sadece bir kez çağırmak ve halihazırda kendisine yapılmakta olan gereksiz çağrıları da kesip atmak istiyoruz. Nihayetinde bu işlev maliyetli bir işlev olduğundan, gerekmedikçe çağrıda bulunmamak, gerekiyorsa da sadece bir kez çağrıda bulunmak istiyoruz.

İşlevleri kullanarak yeniden düzenlemek

Egzersiz programını birçok şekilde yeniden yapılandırabiliriz. Öncelikle, örnek 13-4' te gösterildiği gibi, simulated_expensive_calculation işlevi için tekrarlanan çağrıyı bir değişkene çıkarmayı deneyeceğiz.

Dosya adı: src/main.rs

#use std::thread;
#use std::time::Duration; 
#
#fn simulated_expensive_calculation(intensity: u32) -> u32 {
#    println!("yavaşça hesaplanıyor...");
#    thread::sleep(Duration::from_secs(2));
#    intensity
#}
#
fn generate_workout(intensity: u32, random_number: u32) { 
    let expensive_result =
        simulated_expensive_calculation(intensity);
    if intensity < 25 { 
        println!( 
            "Bugün, {} şınav çek!", 
            expensive_result 
        ); 
        println!( 
            "Sonrasında {} mekik çek!", 
            expensive_result 
        ); 
    } else { 
        if random_number == 3 { 
            println!("Bugün mola ver! Sıvı tüketmeyi de ihmal etme!"); 
        } else { 
            println!( 
                "Bugün, {} dakika koş!", 
                expensive_result 
            ); 
        } 
    }
}
#
#fn main() { 
#    let simulated_user_specified_value = 10;
#    let simulated_random_number = 7;
#    
#    generate_workout(
#        simulated_user_specified_value,
#        simulated_random_number
#    );
#}

Örnek 13-4: simulated_expensive_calculation çağrılarını tek bir yere çıkarmak ve sonucunu expensive_result değişkenine kaydetmek

Bu değişiklik, simulated_expensive_calculation çağrılarını birleştirerek işlevi gereksiz yere iki kez çağıran ilk if bloğunun sorununu çözecektir. Fakat ne yazık ki, bu defa da sonuç değerini hiç kullanmayan iç if bloğu da dahil, her durumda sonucu beklemek zorunda kalıyoruz.

Oysa biz bu kodu, programımızın tek bir yerinde tanımlamak ve sadece sonuca gerçekten ihtiyaç duyduğumuz yerde çalıştırmak istiyorduk. İşte bu durum tam da kapamaların kullanılmasını gerektiren bir durumdur.

Bir kodu kapama işlevi kullanarak yeniden düzenlemek

Her if bloğu öncesinde simulated_expensive_calculation işlevini çağırmak yerine, örnek 13-5'te gösterildiği gibi bir kapama işlevi tanımlayabilir ve bu kapama işlevini bir değişkene depolayarak işlev çağrısı sonucunu saklamaktan kurtulabiliriz. Hatta gerektiğinde simulated_expensive_calculation' ın tüm gövdesini de bu kapama işlevine taşıyabiliriz.

Dosya adı: src/main.rs

#use std::thread;
#use std::time::Duration; 
#
#fn generate_workout(intensity: u32, random_number: u32) { 
    let expensive_closure = |num| {
        println!("yavaşça hesaplanıyor...");
        thread::sleep(Duration::from_secs(2));
        num
    };
#    if intensity < 25 { 
#        println!( 
#            "Bugün, {} şınav çek!", 
#            expensive_closure(intensity) 
#        ); 
#        println!( 
#            "Sonrasında {} mekik çek!", 
#            expensive_closure(intensity) 
#        ); 
#    } else { 
#        if random_number == 3 { 
#            println!("Bugün mola ver! Sıvı tüketmeyi de ihmal etme!"); 
#        } else { 
#            println!( 
#                "Bugün, {} dakika koş!", 
#                expensive_closure(intensity) 
#            ); 
#        } 
#    }
#}
#
#fn main() { 
#    let simulated_user_specified_value = 10;
#    let simulated_random_number = 7;
#    
#    generate_workout(
#        simulated_user_specified_value,
#        simulated_random_number
#    );
#}

Örnek 13-5: Bir kapama işlevinin expensive_closure değişkeninde saklanması

Kapama tanımı expensive_closure değişkenine atanabilmesi için atama operatöründen sonra gerçekleştirilir. Bir kapamanın tanımlanmasına içinde kapama parametrelerinin yer alacağı bir çift dikey boru (|) ile başlanır. Bu sözdizimi, Smalltalk ve Ruby'deki kapama tanımlarına benzediğinden dolayı seçilmiştir. Örneğimizdeki kapama, num adında yalnızca bir parametreye sahip olduğundan |num| biçiminde ifade edilir: Eğer kullanmamız gereken çok sayıda parametremiz olsaydı, bu parametreleri yine çift boru içine |param1, param2| şeklinde virgüllerle ayırırarak kullanmamız gerekecekti.

Parametrelerin ardından, kapama gövdesini tutan kıvrımlı parantezleri yerleştirilir. Eğer kapama gövdesi tek bir ifadeden oluşuyorsa bu parantezleri kullanmak tercihinize bırakılır. let ifadesinin tamamlanabilmesi için kapamanın sonunda, yani kıvrımlı parantezin bitiminde, ; noktalı virgülün kullanılması şarttır. İşlev gövdelerinde olduğu gibi kapama gövdelerindeki son değerler de döndürülen değer statüsünde olduklarından (örneğimizde num) noktalı virgül ile sonlandırılmazlar.

expensive_closure adındaki bu let ifadesinin; isimsiz işlevin çağrılmasıyla oluşan sonuç değerini değil, isimsiz işlev tanımını içerdiğine dikkat edin. Kapamaları: Bir noktada çağrılacak kodu tanımlamak, bu kodu saklamak ve programın ilerleyen safhalarında kendisine yeniden başvurabilmek için kullandığımızı unutmayın. Bu aşamada çağırmak istediğimiz kod artık expensive_closure içinde saklanmaya başlamıştır.

Tanımlanan kapama işleviyle kodu yürüterek oluşan değeri elde etmek için if blokları arasındaki kodu değiştirebiliriz. Kapamaları, örnek 13-6’ da gösterilene benzer şekilde, tıpkı bir işlev çağırıyormuş gibi, kapama tanımını tutan değişken adını verip, parantez içindede alacağı bağımsız değişkenleri belirtirek çağırabiliyoruz.

Dosya adı: src/main.rs

#use std::thread;
#use std::time::Duration; 
#
fn generate_workout(intensity: u32, random_number: u32) { 
    let expensive_closure = |num| {
        println!("yavaş bir hesaplama...");
        thread::sleep(Duration::from_secs(2));
        num
    };
    if intensity < 25 { 
        println!( 
            "Bugün, {} şınav çek!", 
            expensive_closure(intensity) 
        ); 
        println!( 
            "Sonrasında {} mekik çek!", 
            expensive_closure(intensity) 
        ); 
    } else { 
        if random_number == 3 { 
            println!("Bugün mola ver! Sıvı tüketmeyi de ihmal etme!"); 
        } else { 
            println!( 
                "Bugün, {} dakika koş!", 
                expensive_closure(intensity) 
            ); 
        } 
    }
}
#
#fn main() { 
#    let simulated_user_specified_value = 10;
#    let simulated_random_number = 7;
#    
#    generate_workout(
#        simulated_user_specified_value,
#        simulated_random_number
#    );
#}

Örnek 13-6: Tanımladığımız expensive_closure adlı kapama işlevini çağırmak

Şimdi, pahalı hesaplama işlevi sadece tek bir yerde çağrılıyor ve bu kodu, sadece gerçekten sonuçlara ihtiyacımız olan yerde işletmiş oluyoruz.

Bununla birlikte bu defa da, ilk if bloğundaki kapamayı iki kez çağırmakla örnek 13-3'teki sorunlardan biriyle yeniden karşılaşıyor ve bu pahalı kodu ikinci kez çağırılmasıyla, kullanıcının uzun zaman alan iki işlem boyunca beklemesine neden oluyoruz. Bu sorunu ilk if bloğu kapsamında, kapamayı çağıran ve elde ettiği sonucu tutan yerel bir değişken tanımlayarak çözümleyebiliriz. Ancak kapamalar bize başka bir çözüm sağlar. Bu çözüm hakkında konuşmaya başlamadan önce, neden kapama tanımında ek açıklamalar bulunmadığından ve kapalarla ilgili bazı özelliklerden bahsedelim.

Kapamalarda tür çıkarımı ve ek açıklamalar

Kapamalar, fn işlevlerinin gerektirdiği gibi parametre türlerinde veya dönüş değerlerinde açıklama girilmesine ihtiyaç duymazlar. Bununla birlikte standart işlevler, kullanıcılara açık bir arayüzün parçaları olduklarından tür ek açıklamaları gerektirirler. İşlevin ne tür değerler kullandığı veya hangi türden değerler döndürdüğünün, tüm kullanıcılar tarafından açıkça anlaşılabilmesi için, bu arayüzü katı bir şekilde tanımlamak önemlidir. Ancak kapamalar böyle açık bir arayüzde kullanılmak yerine; değişkenlerde depolanmakta, isimsiz olarak kullanılmakta ve kütüphanemizin diğer kullanıcılarına gösterilmeden değerlendirilebilmektedir.

Bir kapama işlevi genellikle kısa ve herhangi bir keyfi senaryodan ziyade, sadece dar bir bağlamda geçerlidir. Bu sınırlı bağlamda derleyici, değişken türlerinin çıkarsanmasına benzer şekilde, kapama parametre ve dönüş türlerini güvenli bir şekilde çıkarsayabilmektedir.

Programcıların bu isimsiz ve küçük işlevlerdeki türlere açıklama eklemesi, derleyicinin zaten sahip olduğu bilgilerle oldukça can sıkıcı ve büyük ölçüde gereksiz olacaktır.

Değişkenlerde olduğu gibi, açıklığı ve netliği gerekli olandan daha ayrıntılı olma pahasına artırmak istiyorsak, kapamalara da tür ek açıklamaları ekleyebiliriz. Örnek 13-5'te tanımladığımız kapama işlevinin kullanacağı türler için ekleyeceğimiz tür ek açıklamaları örnek 13-7' deki kodda gösterilmektedir.

Dosya adı: src/main.rs

#use std::thread;
#use std::time::Duration;

#fn generate_workout(intensity: u32, random_number: u32) {
    let expensive_closure = |num: u32| -> u32 {
        println!("yavaş bir hesaplama...");
        thread::sleep(Duration::from_secs(2));
        num
    };
#
#   if intensity < 25 {
#        println!("Bugün {} şınav çek!", expensive_closure(intensity));
#        println!("Ardından, {} mekik çek!", expensive_closure(intensity));
#    } else {
#        if random_number == 3 {
#            println!("Bugün mola ver! Sıvı tüketmeyi ihmal etme!");
#        } else {
#            println!(
#                "Bugün {} dakika koş!",
#                expensive_closure(intensity)
#            );
#        }
#    }
#}
#
#fn main() {
#    let simulated_user_specified_value = 10;
#    let simulated_random_number = 7;
#
 #   generate_workout(simulated_user_specified_value, simulated_random_number);
#}

Örnek 13-7:Kapama parametre ve dönüş değerlerine isteğe bağlı tür ek açıklamalarını eklemek

Tür ek açıklamaları eklenildiğinde, kapama sözdizimi işlev sözdizimine benzemeye başlıyor. Aşağıda, parametresine 1 değeri ekleyen bir işlev tanımı ile aynı davranışa sahip bir kapama sözdiziminin dikey karşılaştırması yer almaktadır. İlgili parçaları hizalamak için bazı alanlar ekledik. Bu örnekleme; boru kullanımı ve isteğe bağlı sözdizimi haricinde, kapama sözdizimi ile işlev söz diziminin birbirlerine nasıl benzediğini göstermektedir:

fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;

Örneğin ilk satırında bir işlev tanımı, ikinci satırında giriş ve dönüş türleri açıklanan bir kapama tanımı yer almaktadır. Üçüncü satırda, tür açıklamaları kapama tanımından kaldırılırken, dördüncü satırda isteğe bağlı olan parantezler dahi kaldırılmıştır. Hatırlayacağınız gibi bir kapama işlevi tanımlanırken, kapama gövdesi yalnızca bir ifadeden oluştuğunda süslü parantezler kullanılmamaktaydı. Yukarıda sunduğumuz kapama ifadelerinin her biri, çağrıldığında aynı davranışı üretecek geçerli tanımlamalardır.

Kapama tanımlarında, her bir parametre ve dönüş değeri için somut bir tür hesaplanır. Örnek 13-8, yalnızca parametre olarak aldığı değeri döndüren kısa bir kapama tanımını göstermektedir. Bu kapama, sadece bu örneği sunabilmek için tasarlandığından gerçek kullanım için uygun değildir. Kapamayı ik kez çağırdığımızda, dizgiyi bir argüman olarak geçirmeyi başarırız, ancak u32 türü kullanan ikinci denememiz bir hata ile sonuçlanacaktır. Tanıma herhangi bir tür ek açıklaması eklenmediğine dikkat ediniz.

Dosya adı: src/main.rs

let example_closure = |x| x; 
let s = example_closure(String::from("hello")); 
let n = example_closure(5);

Örnek 13-8: Girdi ve çıktı değerlerinin, iki farklı tür üzerinden çıkarsanması beklenen bir kapama örneği

Derleyici bize şu hatayı verir:

error[E0308]: mismatched types
 --> src/main.rs
  |
  | let n = example_closure(5);
  |                         ^ expected struct `std::string::String`, found
  integer
  |
  = note: expected type `std::string::String`
             found type `{integer}`

Kapama işlevimiz olan example_closure dizgi değeri ile çağrıldığında, derleyici x parametresi ve dönüş türünü dizgi olarak algılar. Algılanan bu türler daha sonra example_closure içindeki kapamaya kilitlenir ve aynı kapama ile farklı bir tür kullanmaya çalışıldığında bir uyumsuz tür hatası ile karşılaşılır.

Jenerik parametreler ve Fn özelliklerini kullanarak kapamaları hafızaya almak

Egzersiz planı üreten uygulamamıza geri dönecek olursak; örnek 13-6' daki kodumuz, halen pahalı hesaplama yapan kapama işlevimizi gerekenden daha fazla çağırmaktadır. Bu sorunu çözmenin bir yolu; maliyetli kapama sonucunu daha sonra yeniden kullanabilmek için bir değişkene kaydetmek ve kapama işlevini tekrar tekrar çağırmak yerine, sonuca ihtiyacımız olan yerde bu değişkeni kullanmaktır. Ancak, bu yöntem de çok sayıda kod tekrarı yapılmasını gerektirir.

Neyse ki bizim için başka bir çözüm yolu daha var. Kapamayı tutacak bir yapı tanımlayıp, kapama çağrıldığında sonuç değerini o yapı üzerinden oluşturabiliriz. Tanımlayacağımız bu yapı kapamayı yalnızca sonuç değerine ihtiyaç duyduğumuzda işleterek sonucunu önbelleğe alacak, böylelikle kodun geri kalanı sonucu kaydetmek ve yeniden kullanmak zorunda kalmamış olacaktır. Bu kalıbı daha önceki tecrübelerinizden, tembel değerlendirmeler veya ezberleme olarak biliyor olabilirsiniz.

Kapama tutabilen yapıların tanımlanabilmesi için, kapama işlevinin giriş ve dönüş türlerinin bildirilmesi gereklidir. Çünkü yapılar oluşturulurken sahip oldukları alanlar isimlendirildiklerinde türlerinin de bildirilmesi gerekmektedir. Her kapama örneğinin kendine özgü isimsiz türü olacağından, iki kapama aynı imzaya sahip olsalar bile, farklı türlerde oldukları kabul edilmektedir. Kapamaları kullanan yapı, enum veya işlev parametrelerini tanımlarken Bölüm 10'da tartıştığımız jenerikler ve özellik sınırlarını da kullanabiliyoruz.

Fn özellikleriyse standart kütüphane tarafından sağlanmaktadır ve tüm kapama işlevleri; Fn, FnMut veya FnOnce özelliklerinden en az birini uygular. Bu özelliklerin arasındaki farkları "Kapamalar ile ortam bilgilerini elde etmek" bölümünde tartışacağız; bu örnek için, Fn özelliğini kullanmamızda sakınca yok.

Parametrelerin türlerini temsil etmek ve kapamaların bu özellik sınırıyla eşleşmesi gereken değerleri döndürmek için Fn özelliğine bağlı türler ekliyoruz. Bu durumda, kapama işlevinin u32 türünde bir parametresi olacak ve bir u32 türü döndüreceğinden belirttiğimiz özellik sınırı Fn (u32) -> u32 olacaktır.

Örnek 13-9, bir kapama ve opsiyonel sonuç değeri tutan Cacher yapısının tanımını gösterir.

Dosya adı: src/main.rs

struct Cacher<T>
    where T: Fn(u32) -> u32
{
    calculation: T,
    value: Option<u32>,
}
#fn main() {}

Örnek 13-9: Bir calculation ve opsiyonel sonuç değerinden oluşan kapamayı tutan Cacher adlı yapının tanımlanması

Cacher yapısı, T türünde jenerik bir hesaplama alanına sahiptir. T üzerindeki özellik sınırları, bunun Fn özelliğini kullanmakta olan bir kapama olduğunu belirtir. Yapının calculation adlı hesaplama alanında saklamak istediğimiz tüm kapamaların u32 türünden bir parametresi (Fn'den sonra parantez içinde belirtilir) bulunmalı ve bu kapamadan bir u32 türünde (-> işaretinden sonra belirtilir) değer döndürülmelidir.

Not: İşlevler Fn özelliklerinin üçünü de uygulayabilir. Yapmak istediğimiz şey, ortamdan bir değer yakalamayı gerektirmiyorsa ve Fn özelliğini uygulayan bir şeye ihtiyacımız varsa kapatma yerine işlev kullanmayı tercih edebiliriz.

Yapının value adındaki alanı Option<u32> türündedir. Kapama işletilmeden önce bu alan None varyantını göstermektedir. Eğer Cacher yapısını kullanan bir program kapamanın sonucunu isterse, yapı içerisindeki kapama işletilecek, bu defa da oluşan sonuç değeri value alanının Some varyantı içinde saklanacaktır. Eğer kapamanın sonucu bu program tarafından yeniden talep edilirse, sonuç zaten depolanmış olduğundan kapama tekrar işletilmeyecek, bu yapının Some varyantında tutulan değer döndürülecektir.

Az önce tanımladığımız value alanının mantığı örnek 13-10' da gösterilmektedir.

Dosya adı: src/main.rs

struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

#fn main() {}

Örnek 13-10: Cacher yapısının önbellek mantığı

Bu yapıyı çağıracak olan kodun alanlardaki değerleri doğrudan değiştirmesini tercih etmek yerine, sadece yapı alanlarının değerleriyle ilgilenmesini istediğimizden bu alanları dışarıdan erişime kapatarak özelleştiriyoruz.

Cacher::new işlevi, Cacher yapısıyla aynı özelliğe bağlı olarak tanımladığımız jenerik T parametresi alır. Daha sonra kapama işlevini henüz gerçekleştirmemiş olduğundan calculation alanında belirtilen kapama değeri ve value alanındaki None değerinden oluşan bir Cacher örneği döndürür.

Böylelikle kodu çağıran taraf, kapama işleminden elde edilen sonuca ihtiyaç duyduğunda, kapamayı doğrudan çağırmak yerine value yöntemini çağırmış olacaktır. Bu yöntem, Some varyantında self.value türünde bir değere sahip olup olmadığımızı kontrol eder. Bu değere sahipsek kapama bir daha işletilmez ve Some içinde depolanmakta olan değer döndürülür.

Ancak self.value değeri None olarak görünüyorsa, kod self.calculation'da depolanan kapamayı çağıracak, sonucu ileride kullanılmak üzere self.value'ye kaydedecek ve oluşan değeri çağıran tarafa döndürecektir.

Aşağıdaki örnek, Cacher yapısını örnek 13-6'da bulunan create_workout işlevinde nasıl kullanabileceğimizi göstermektedir.

Dosya adı: src/main.rs

#use std::thread;
#use std::time::Duration; 
#
#struct Cacher<T>
#    where T: Fn(u32) -> u32
#{
#    calculation: T,
#    value: Option<u32>,
#}
#
#impl<T> Cacher<T>
#    where T: Fn(u32) -> u32
#{
#    fn new(calculation: T) -> Cacher<T> {
#        Cacher {
#            calculation,
#            value: None,
#        }
#    }
#
#    fn value(&mut self, arg: u32) -> u32 {
#        match self.value {
#            Some(v) => v,
#            None => {
#                let v = (self.calculation)(arg);
#                self.value = Some(v);
#                v
#            },
#        }
#    }
#}
#
fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("yavaşça hesaplanıyor...");
        thread::sleep(Duration::from_secs(2));
        num
    });

    if intensity < 25 {
        println!(
            "Bugün, {} şınav çek!",
            expensive_result.value(intensity)
        );
        println!(
            "Sonrasında {} mekik çek!",
            expensive_result.value(intensity)
        );
    } else {
        if random_number == 3 {
            println!("Bugün mola ver! Sıvı tüketmeyi de ihmal etme!");
        } else {
            println!(
                "Bugün, {} dakika koş!",
                expensive_result.value(intensity)
            );
        }
    }
}
#
#fn main() { 
#    let simulated_user_specified_value = 10;
#    let simulated_random_number = 3;
#    
#    generate_workout(
#        simulated_user_specified_value,
#        simulated_random_number
#    );
#}

Örnek 13-11:Önbellek mantığını soyutlamak için generate_workout işlevinde Cacher yapısını kullanmak

Böylelikle kapamayı doğrudan bir değişkene kaydetmek yerine, bu kapamayı tutması için yeni bir Cacher örneğini kaydediyoruz. Bu noktadan itibaren sonuca ihtiyacımız olan her yerde, Cacher yapısının bir örneğini oluşturup, value metodunu çağırırarak tembelce hesaplanan sonuca ulaşırız. Ayrıca pahalı hesaplama sonucunu döndüren expensive_result işlevi en fazla bir kez çağırılacağından value yöntemini çağırmak tercihimize kalmıştır.

Bu programı örnek 13-2' deki main işleviyle çalıştırmayı deneyin. Tüm if ve else bloklarında, yavaşça hesaplanıyor... çıktısının sadece bir kez ve gerektiğinde göründüğünü test edebilmeniz için simulated_user_specified_value ve simulated_random_number değişken değerlerini dilediğiniz kadar değiştirebilirsiniz. Cacher ön bellek yapısı, pahalı hesaplamayı ihtiyacımız kadar çağırarak create_workout iş mantığına rahatlıkla odaklanabilir.

Cacher Uygulamasının Kısıtlamaları

Değerleri önbelleğe almak, kodumuzun diğer bölümlerinde genellikle farklı kapamalarda kullanmak isteyebileceğimiz yararlı bir davranıştır. Bununla birlikte, Cacher'in mevcut uygulamasında, farklı bağlamlarda yeniden kullanılmasını zorlaştıracak iki sorun bulunmaktadır.

İlk sorun, bir Cacher örneğinin value metodunda bulunan arg parametresinin her zaman aynı değeri alacağı varsayılır. Yani, bu Cacher testi başarısız olacaktır:

#struct Cacher<T>
#where
#    T: Fn(u32) -> u32,
#{
#    calculation: T,
#    value: Option<u32>,
#}
#
#impl<T> Cacher<T>
#where
#    T: Fn(u32) -> u32,
#{
#    fn new(calculation: T) -> Cacher<T> {
#        Cacher {
#            calculation,
#            value: None,
#        }
#    }
#
#    fn value(&mut self, arg: u32) -> u32 {
#        match self.value {
#            Some(v) => v,
#            None => {
#                let v = (self.calculation)(arg);
#                self.value = Some(v);
#                v
#            }
#        }
#    }
#}
#
#[cfg(test)]
#mod tests {
#    use super::*;
#
    #[test]
    fn call_with_different_values() {
        let mut c = Cacher::new(|a| a);

        let v1 = c.value(1);
        let v2 = c.value(2);

        assert_eq!(v2, 2);
    }
}

Bu testte, kendisine iletilen değeri döndüren bir kapamayla yeni bir Cacher örneği oluşturulmaktadır. Örneğin value metodunu arg parametre değeri olarak önce 1, ardından 2 vererek çağırdığımızda; arg 2 değeriyle yaptığımız çağrının 2 değerini döndürmesini bekleriz.

Oysa bu testi örnek 13-9 veya 13-10’daki Cacher uygulaması ile gerçekleştirdiğimizde program assert_eq!' de başarız olacak ve şu hata mesajını döndürecektir:

$ cargo test
   Compiling cacher v0.1.0 (file:///projects/cacher)
    Finished test [unoptimized + debuginfo] target(s) in 0.72s
     Running target/debug/deps/cacher-4116485fb32b3fff

running 1 test
test tests::call_with_different_values ... FAILED

failures:

---- tests::call_with_different_values stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `2`', src/lib.rs:43:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.


failures:
    tests::call_with_different_values

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--lib'

Buradaki sorun, başlangıçta c.value'yu 1 ile çağırdığımızda, Catcher örneğinin Some(1) değerini self.value içine kaydetmesinden kaynaklanmaktadır. Bu noktadan sonra, value yöntemine hangi değeri iletirsek iletelim, her zaman başlangıçta verdiğimiz 1 değerini döndürecektir.

Cacher'ı tek bir değer yerine bir eşleme tablosu tutacak şekilde değiştirmeyi deneyin. value metoduna geçirilecek arg değerleri eşleme tablosunun anahtarlarını oluşturacak şekilde verildiğinde, eşleme tablosunun değerlerinde de kapama çağrılarının sonuçları tutulmuş olacaktır. Böylece self.value öğesinin doğrudan Some veya None değeri olup olmadığını kontrol etmek yerine, value işlevi eşleme tablosunun anahtarlarında arg öğesini arayacak bulduğu anda değerini döndürecektir. Eğer arg öğesi tabloda yoksa, Cacher tarafından kapama çağırılacak ve oluşan değer, arg değeriyle ilişkili eşleme tablosuna kaydedecektir.

Bu uygulamadaki ikinci sorun ise yalnızca u32 türünde parametre alması ve u32 türünde değer döndüren kapamaları kabul etmesidir. Örneğin, bir dizgi dilimi alan ve usize değerleri döndüren kapama sonuçlarını önbelleğe almak isteyebiliriz. Bu sorunu gidermek ve Cacher işlevinin esnekliğini artırmak için jenerik parametreler eklemeyi deneyin.

Kapamalar ile ortam bilgilerini elde etmek

Egzersiz planı oluşturan örneğimizde, kapamaları sadece satır içi isimsiz işlevler olarak kullandık. Bununla birlikte kapamalar, işlevlerin sahip olmadığı ek bir yeteneğe sahiplerdir: ortam bilgilerini yakalayabilir ve tanımlandıkları kapsamdan değişkenlere erişebilirler.

Örnek 13-12'de, tanımlandığı kapsamda bulunan x değişkenini equal_to_x adlı değişkene depolayarak kullanan bir kapama örneği sunulmaktadır.

Dosya adı: src/main.rs

 fn main() {
    let x = 4;

    let equal_to_x = |z| z == x;

    let y = 4;

    assert!(equal_to_x(y));
}

Örnek 13-12: Tanımlandığı kapsam içindeki bir değişkene başvuran kapama örneği

Burada x değişkeni, equal_to_x kapama parametrelerinden biri olmamasına rağmen, equal_to_x kapamasının, kendisiyle aynı kapsamda tanımlanan x değişkenini kullanmasına izin verilmektedir.

Oysa aynı şeyi işlevlerle gerçekleştiremeyiz. Aşağıdaki örnek kod derlenmeyecektir:

Dosya adı: src/main.rs

fn main() {
    let x = 4;

    fn equal_to_x(z: i32) -> bool { z == x }

    let y = 4;

    assert!(equal_to_x(y));
}

Alacağımız hata aşağıdaki gibidir:

$ cargo run
   Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x)
error[E0434]: can't capture dynamic environment in a fn item
 --> src/main.rs:5:14
  |
5 |         z == x
  |              ^
  |
  = help: use the `|| { ... }` closure form instead

error: aborting due to previous error

For more information about this error, try `rustc --explain E0434`.
error: could not compile `equal-to-x`.

To learn more, run the command again with --verbose.

Derleyici bize bu kodun sadece kapamalarla çalıştığını anımsatıyor!

Kapamalar ortamlarından bir değer yakaladıklarında, bu değerleri kapama gövdesinde kullanmak üzere saklamak için bellek kullanmak zorundadırlar. Bu bellek kullanımı, daha yaygın kullanım örneklerinde olduğu gibi, ortamlarını yakalamayan kodların işletilmesindeki maliyetten daha fazladır. İşlevlerin ortamlarını yakalamalarına asla izin verilmediğinden, işlevlerin tanımlanması ve kullanılması böyle bir ek yüke neden olmaz.

Kapamalar çevrelerindeki değerleri üç şekilde yakalayabilirler ve bunlar bir işlevin parametre alacağı üç yolla doğrudan eşleşir: Mülkiyeti üzerlerine alırken, değişebilir borçlanma ve değişmez borçlanma. Bunlar aşağıdaki sunulan üç Fn özelliğinde kodlanmıştır:

  • FnOnce kapama ortamı olarak bilinen, çevrelendiği kapsamdan yakaladığı değişkenleri tüketir. Kapamanın yakaladığı değişkenleri tüketebilmesi için bu değişkenlerin mülkiyetini alması ve tanımına taşıması gerekmektedir. İsmin Once adlı parçası, kapamanın aynı değişkenlerin mülkiyetini sadece bir kez alabileceğini gösterdiğinden, bu özellik yalnızca bir kez çağrılabilir.
  • FnMut Değişebilen değerleri ödünç aldığı için ortamı değiştirebilir.
  • Fn ortamdaki değerleri değişmez olarak ödünç alır.

Bir kapama oluşturduğunuzda, Rust, kapamanın ortamdaki değerleri nasıl kullandığına bağlı olarak hangi özelliğin kullanılacağını otomatik olarak belirler. Tüm kapamalar FnOnce'i uygular, çünkü hepsi en az bir kez çağrılabilir. Yakalanan değişkenleri taşımayan kapamalar da FnMut'u uygularlar. Yakalanan değişkenlere değiştirilebilir erişim gerektirmeyen kapamalar ise Fn'i uygularlar. Örnek 13-12'de equal_to_x değişkenine depolanan kapama, değişmez olarak tanımlanan x değişkenini, değişmez olarak ödünç alır; yani equal_to_x Fn özelliğine sahiptir, çünkü kapama gövdesinin sadece x değerini okuması gerekmektedir.

Kapamaları ortamlarından kullandığı değerlerin mülkiyetini almaya zorlamak istiyorsanız, parametre listesinden önce move anahtar sözcüğünü kullanabilirsiniz. Bu teknik, verilerin mülkiyetlerinin işlenmek üzere yeni iş parçasına aktarırken oldukça yararlıdır.

Bölüm 16’da eşzamanlılık hakkında konuşurken, kapamaların taşınması hakkında daha fazla örnek vereceğiz. Ancak şimdilik, tam sayılar taşınmak yerine kopyalandıklarından, bunların yerine vektör kullanan ve tanımına move anahtar sözcüğü ekleyerek yeniden düzenlediğimiz kapama işlevini gösteren örnek 13-12'yi sunalım. Bu kodun henüz derlenmeyeceğini unutmayın:

Dosya adı: src/main.rs

fn main() {
    let x = vec![1, 2, 3];

    let equal_to_x = move |z| z == x;

    println!("can't use x here: {:?}", x);

    let y = vec![1, 2, 3];

    assert!(equal_to_x(y));
}

Aldığımız hata aşağıdaki gibidir:

$ cargo run
   Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x)
error[E0382]: borrow of moved value: `x`
 --> src/main.rs:6:40
  |
2 |     let x = vec![1, 2, 3];
  |         - move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
3 | 
4 |     let equal_to_x = move |z| z == x;
  |                      --------      - variable moved due to use in closure
  |                      |
  |                      value moved into closure here
5 | 
6 |     println!("can't use x here: {:?}", x);
  |                                        ^ value borrowed here after move

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
error: could not compile `equal-to-x'.

To learn more, run the command again with --verbose.

Kapama tanımlanırken move anahtar sözcüğü eklediğimizden x değeri kapamaya taşınır. Artık x'in mülkiyeti kapamaya ait olduğundan main işlevindeki println! ifadesinde x'i kullanılmasına izin verilmez. Sorunun giderilmesi için println! ifadesinin kaldırılması yeterli olacaktır.

Nihayetinde derleyici, kapama gövdesini analiz ederek FnMut veya FnOnce özelliklerinden hangisine ihtiyacınız olup olmadığını söyleyeceğinden, özellik sınırlarından birini belirlerken, Fn ile başlamak çoğu zaman iyi fikirdir.

Ortamlarını yakalayabilen kapamaların işlev parametreleri olarak kullanılmasının yararlı olduğu durumları daha iyi gösterebilmek için bir sonraki Yineleyiciler başlığımıza geçelim.

Yineleyiciler ile Bir Dizi Öğeyi İşlemek

Bir koleksiyonun tüm elemanları tükenene kadar her bir elemanı üzerinde sırasıyla belirli işlemleri gerçekleştirmekten yineleyiciler sorumludurlar. Yineleyici kullandığınızda bütün bu işlemlerin her birini tekrar tekrar gerçekleştirmek zorunda kalmazsınız.

Rust'ta yineleyiciler tembel olduklarından, kendilerini tüketen yöntemler çağırılana kadar programlarınızı etkilemezler. Örneğin aşağıdaki kod; Vec<T> üzerinde tanımlanan iter yöntemini çağırarak v1 vektöründeki öğeler üzerinde bir yineleyici oluşturur. Bu kod tek başına anlamlı bir şey gerçekleştirmez.

Dosya adı: src/main.rs

fn main() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();
}

Örnek 13-13: Bir yineleyici oluşturmak

Bir yineleyici oluşturduktan sonra artık bunu çeşitli şekillerde kullanabiliriz. Bölüm 3'ün sonlarında yer alan örnek 3-5'te, her eleman üzerinde bir takım işlemleri gerçekleştirmek amacıyla for döngüsünün tüketimine verilen iter tanımlamasıyla yineleyici kullanmış, ancak yineleyicilere şu ana kadar derinlemesine odaklanamamıştık.

Aşağıdaki örnekte, yineleyicinin oluşturulması ile for döngüsünde kullanımı birbirinden bağımsız olarak sunulmaktadır. Kendi başına v1_iter değişkeninde saklanan yineleyicinin tanımlandığı satırda hiç bir işlem gerçekleşmezken, bu değişkene adapte edilmiş yineleyiciyi kullanan bir for döngüsü ile çağrıldığında, kendisine geçirilmiş olan her öğenin değerini ekrana yazdıran bir döngü yineleyicisi olarak kullanışlı hale gelir.

Dosya adı: src/main.rs

fn main() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();

    for val in v1_iter {
        println!("Okunan: {}", val);
    }
}

Örnek 13-14: Bir yineleyici for döngüsünde kullanmak

Standart kitaplıklarında yineleyici bulunmayan dillerde bu tarz bir işlev, olasılıkla dizinin ilk elemanıyla değerinin arttırıldığı bir sayaç değişkeniyle başlatılacak, dizinin sonuna erişilene kadar her eleman için değişken birer birer güncellenerek işletilecektir.

Oysa yineleyiciler bütün bu karmaşık sayım sürecindeki mantığı sizin için üstlenebilir, muhtemel kod tekrarlarını azaltarak potansiyel karışıklıkların üstesinden gelebilirler. Yineleyiciler sadece vektörler gibi indekslenebilir veri yapılarıyle değil, aynı mantığın uygulandığı pekçok farklı koleksiyon türüyle kullanılılırken de fazlasıyla esnektirler. Haydi yineleyicilerin bunu nasıl yaptığını birlikte inceleyelim.

Iterator Özelliği ve next Metodu

Tüm yineleyiciler standart kitaplıkta tanımlanan Iterator adlı bir özelliği uygular. Özelliğin tanımı şuna benzer:

#![allow(unused_variables)]
fn main() {
    pub trait Iterator {
        type Item;

        fn next(&mut self) -> Option<Self::Item>;

        // Şu an için varsayılan uygulamaları gösterilmeyen metodlar
    }
}

Bu tanımda type Item ve Self::Item gibi bu özelliklerle ilişkilendirilmiş türü bildiren yeni söz dizimleri kullanıldığına dikkat edin. İlişkili türlerden bölüm 19’da ayrıntılı olarak bahsedeceğimizden şimdilik bilmeniz gereken tek şey; bu kodun yineleyici özelliğini (Itarator trait) uygulayabilmek için bir öğe türü (Item type) tanımlanması gerektiği ve bu öğe türünün next metodunun dönüş türünde kullanıldığını belirtmesidir. Başka bir deyişle öğe türü yineleyiciden döndürülen tür olacaktır.

Yineleyici özelliği uygulayıcılara sadece bir metodu tanımlamak için ihtiyaç duyar. Tanımlanan next metodu yineleme devam ettiği sürece öğeleri Some ile sarmalayarak birer birer döndürürken, yineleme sona erdiğinde None döndürecektir.

Yineleyicideki next metodunu doğrudan kendimiz de çağırabiliriz. Örnek 13-15'te, v1 vektöründe oluşturulan yineleyiciye yapılan çağrılardan next metoduna hangi değerlerin döndürüldüğü gösterilmektedir.

Dosya adı: src/lib.rs


#[cfg(test)]
mod tests {
    #[test]
    fn iterator_demonstration() {
        let v1 = vec![1, 2, 3];

        let mut v1_iter = v1.iter();

        assert_eq!(v1_iter.next(), Some(&1));
        assert_eq!(v1_iter.next(), Some(&2));
        assert_eq!(v1_iter.next(), Some(&3));
        assert_eq!(v1_iter.next(), None);
    }
}

fn main() {}

Örnek 13-15: Yineleyicideki next metodunu çağırmak

Öncelikle v1_iter değişmezinin mut anahtar sözcüğüyle değişebilir hale dönüştürülmesi gerektiğine dikkat edin. Bir yineleyicide next metodunun çağrılması, yineleyicinin dizide bulunduğu yeri izlemek için kullandığı iç konumu değiştirir. Başka bir deyişle, metodu çağıran kod yineleyiciyi tüketir veya kullanır. Her next metodu çağrısı yineleyicide bir öğenin tüketilmesine neden olur. Oysa v1_iter değişkeni for döngüsü ile kullanıldığında, değişkenin mülkiyeti döngüye aktarıldığından, durumu perde arkasında değişebilir olarak değiştirilir ve böylelikle v1_iter değişmezinin değişebilir olarak dönüştürülmesine gerek duyulmaz.

Ayrıca next metodu çağrılarından aldığımız değerlerin, vektör elemanlarının değişmez referansları olduğunu ve Iter metodunun değişmez referanslar üzerinde bir yineleyici oluşturduğunu unutmayın. Eğer v1 vektörünün mülkiyetini alarak, sahip olunan değerleri döndürecek bir yineleyici oluşturmak istiyorsanız, iter yerine into_iter metodunu çağırabilirsiniz. Benzer şekilde, değişebilir referanslar üzerinde yineleme yapmanız gerektiğinde, iter kullanmak yerine, iter_mut metodunu kullanabilirsiniz.

Yineleyici Tüketen Metodlar

Standart kitaplık tarafından sağlanan Iterator özelliğinin varsayılan uygulaması, halihazırda bir dizi metoda sahip olduğundan, bu metodlar hakkındaki bilgilere Iterator özelliğinin API belgelerini inceleyerek ulaşabilirsiniz. Bu metodlardan bazıları tanımlarında bulunan next metodunu çağırdığından Iterator özelliğini uygularken bu metodu da uygulamanız gerekmektedir.

Bu metodunu çağıran işlevler ise çağrıları sırasında yineleyiciyi kullandıklarından onlara tüketici adaptörleri adı verilir. Yineleyicinin mülkiyetini alarak her öğe için next() metod çağırısını yineleyen ve bu esnada yineleyiciyi tüketen sum metodu buna iyi bir örnektir. Bu metod yineleme süresince her elemanı toplama ekleyer ve yineleme sona erdiğinde bu toplamı döndürür. Örnek 13-16 sum metodu kullanımını örnekleyen bir test içerir.

Dosya adı: src/lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn iterator_sum() {
        let v1 = vec![1, 2, 3];

        let v1_iter = v1.iter();

        let total: i32 = v1_iter.sum();

        assert_eq!(total, 6);
    }
}

fn main() {}

Örnek 13-16: Yineleyicideki tüm öğelerin toplamını alan sum metodunu çağırmak

sum metodu, kendisine yapılan çağrı ile v1_iter değişmezindeki yineleyicinin mülkiyetini üzerine alacağından bu değişkenin kullanılmasına izin verilmez.

Diğer Yineleyicileri Üreten Metodlar

Iterator özelliğinde tanımlanan ve yineleyici adaptörleri adı verilen diğer yöntemler ise yineleyicileri farklı yineleyicilerle değiştirmenize olanak sağlarlar. Bu adaptörlere karmaşık eylemleri okunabilir şekilde gerçekleştirmek çok sayıda çağrı zincirleyebilirsiniz. Ancak tüm yineleyiciler tembel olduklarından, yineleyici adaptörlerine yapılan çağrılardan sonuç alabilmek için tüketici adaptörlerinden birini çağırmanız gerekir.

Örnek 13-17'de yeni bir yineleyici üretmek üzere her öğenin çağrıldığı bir kapama işlevine sahip yineleyici adaptörü olan map metodunun bir örneği gösterilir. Buradaki kapama işlevi vektördeki her öğe değerinin 1 arttırıldığı yeni bir yineleyici oluşturacaktır. Ancak bu kod bir uyarı vermektedir.

Dosya adı: src/main.rs


fn main() {
    let v1: Vec<i32> = vec![1, 2, 3];

    v1.iter().map(|x| x + 1);
}

Bir yineleyici adaptörü olan map metodunu yeni bir yineleyici oluşturmak üzere çağırmak

Aldığımız uyarının çıktısı aşağıdaki gibidir:


$ cargo run
   Compiling iterators v0.1.0 (file:///projects/iterators)
warning: unused `std::iter::Map` that must be used
 --> src/main.rs:4:5
  |
4 |     v1.iter().map(|x| x + 1);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: iterators are lazy and do nothing unless consumed

    Finished dev [unoptimized + debuginfo] target(s) in 0.47s
     Running `target/debug/iterators`

Örnek 13-17'de yer alan kod hiçbir şey yapmadığı gibi bildirdiğimiz kapama işlevi de hiçbir zaman çağrılmaz. Yineleyici adaptörleri tembel olduklarından, derleyicinin uyarısı bize yinelecinin tüketilmesi gerektiği uyarısını yapıyor.

Bu durumu düzeltmek ve yineleyiciyi kullanabilmek için Bölüm 12, Örnek 12-1'de yer alan ve env::args ile kullandığımız collect metodundan yararlanacağız. Bu metod yineleyiciyi tüketerek elde ettiği değerleri bir koleksiyon veri türüne depolar.

Örnek 13-18'de map metoduna yapılan çağrı vasıtasıyla yineleyici üzerinde yapılan yinelemeden döndürülen sonuçları bir vektörde topluyoruz. Sonuçların depolandığu bu vektör, orijinal vektördeki her öğenin değerine 1 eklenmiş sayılardan oluşmaktadır.

Dosya adı: src/main.rs


fn main() {
    let v1: Vec<i32> = vec![1, 2, 3];

    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

    assert_eq!(v2, vec![2, 3, 4]);
}

Örnek 13-18: Yeni bir yineleyici oluşturmak üzere map yöntemini ve üretilen bu yineleyiciyi vektör oluştururken tüketen collect yöntemini çağırmak

map metodu bir kapama işlevi aldığından, her bir öğe için uygulamak istediğimiz herhangi bir işlemi belirtebiliriz. Bu örnek, Iterator özelliğinin sağladığı yineleme davranışını yeniden kullanırken, kapamaların bazı davranışları özelleştirmenize nasıl izin verdiğini gösteren harika bir örnektir.

Ortamlarını Yakalayan Kapamalar Kullanmak

Artık yineleyicileri kullanıma sunduğumuza göre, bir yineleyici adaptörünü olan filter metodu kullanarak ortamlarını yakalayan kapamaların yaygın bir kullanımını gösterebiliriz. Bir yineleyicideki filter metodu, yineleyiciden aldığı her öğe karşılığında Boolean döndüren bir kapama işlevini kullanır. Kapama true döndürdüğünde, değer filter tarafından üretilen yineleyiciye dahil edilecek, false döndürdüğündeyse yineleyiciye dahil edilmeyecektir.

Örnek 13-19'da Shoe adlı yapı örneklerinden oluşan koleksiyon üzerinde yineleme yapmak üzere shoe_size değişkenini ortamından elde eden bir kapama işleviyle filter metodunu birlikte kullanıyor ve sadece belirtilen boyuttaki ayakkabıları döndürüyoruz.

Dosya adı: src/lib.rs

#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn filters_by_size() {
        let shoes = vec![
            Shoe {
                size: 10,
                style: String::from("Spor ayakkabı"),
            },
            Shoe {
                size: 13,
                style: String::from("Sandalet"),
            },
            Shoe {
                size: 10,
                style: String::from("Bot"),
            },
        ];

        let in_my_size = shoes_in_my_size(shoes, 10);

        assert_eq!(
            in_my_size,
            vec![
                Shoe {
                    size: 10,
                    style: String::from("Spor ayakkabı")
                },
                Shoe {
                    size: 10,
                    style: String::from("Bot")
                },
            ]
        );
    }
}

fn main() {}

Örnek 13-19: shoe_size değerini ortamından yakalayan bir kapama ile filter metodunu birlikte kullanmak

shoes_in_my_size işlevi, parametre olarak bir ayakkabı vektörü ve bir ayakkabı numarasının mülkiyetini alarak sadece belirtilen ölçüdeki ayakkabıları içeren bir yeni vektör döndürür.

shoes_in_my_size işlevinin gövdesinde vektörün mülkiyetini alacak bir yineleyici oluşturmak üzere into_iter metodunu çağırıyor, sonra bu yineleyiciyi, kapamanın sadece true döndürdüğü öğelerden oluşan yeni bir yineleyiciye uyarlayamak amacıyla filter metodunu kullanıyoruz.

Ortamdan shoe_size parametresini yakalayan kapama, bu değeri her bir ayakkabının numarasıyla karşılaştırarak yalnızca belirtilen ölçüdeki ayakkabıları tutar. Son olarak, collect çağrısı, uyarlanmış yineleyici tarafından döndürülen değerleri işlev tarafından döndürülen bir vektöre depolar.

Örneğimizdeki test, shoes_in_my_size işlevini çağırdığımızda, yalnızca belirttiğimiz ölçülere uygun ayakkabıların döndürüldüğünü göstermektedir.

Iterator Özelliği ile Kendi Yineleyicilerimizi Oluşturmak

Bir vektör üzerinde iter, into_iter veya iter_mut metodlarını çağırarak bir yineleyici oluşturabileceğinizi gösterdik. Tıpkı bu vektör için oluşturduğumuz yineleyici gibi, standart kütüphanedeki eşleme haritaları veya diğer koleksiyon türleri için de yineleyiciler hazırlayabilir, Iterator özelliğini kendi türlerinize uygulayarak dilediğiniz işlemleri gefçekleştiren yineleyiciler oluşturabilirsiniz. Daha önce de belirtildiği gibi, tanımlamanız gereken tek metod next metodu olduğundan, bu metodu tanımladığınızda, Iterator özelliği tarafından sağlanan varsayılan uygulamalara sahip metodların tümünü kullanabilirsiniz!

Bu bilgiyi pekiştirmek adına, sadece 1'den 5'e kadar sayacak bir yineleyici oluşturalım. Öncelikle bunun için, bazı değerleri tutan bir yapı oluşturacak, ardından bu yapıya Iterator özelliğini uygulayacak ve bu uygulamadaki değerler vasıtasıyla bu yapıyı bir yineleyici haline getireceğiz.

Örnek 13-20, Counter yapısının tanımını ve bu yapının örneklerini oluşturmak amacıyla ilişkilendirilmiş new() işlevini göstermektedir:

Dosya adı: src/lib.rs

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

fn main() {}

Örnek 13-20: Counter yapısı ve count alanı başlangıç değeri 0 olan yapı örnekleri başlatan new işlevini tanımlamak

Counter yapısının count adlı bir alanı vardır. Bu alan, 1'den 5'e kadar olan yineleme sürecinde nerede olduğumuzu takip edecek u32 türünden bir değer tuttuğundan ve count uygulamasının değerini yöneteceğinden özeldir. new işlevi ise her yeni örnek başlatıldığında, başlatılan bu örnekleri count alanı sayesinde daima 0 değeriyle ilklendirmeye çalışır.

Daha sonra, Örnek 13-21'de gösterildiği gibi, bu yineleyici kullanıldığında üstleneceği görevleri bildiren next metodunun gövdesini tanımlayarak Counter türüne Iterator özelliğini uygulayacağız:

Dosya adı: src/lib.rs


struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

fn main() {}

Örnek 13-21: Counter yapısına Iterator özelliğini uygulamak

Yineleyicimiz için ilişkili öğe türünü u32 olarak belirleyip type Item = u32; şeklinde ayarladığımızdan yineleyiciden u32 türünde değerler döndürülecektir. Bu noktada İlişkili Türler konusunu Bölüm 19'da ele alacağımızı hatırlatarak konuya devam ediyoruz.

Yineleyicimizin mevcut duruma 1 eklemesini istediğimizden, 1'i döndürebilmesi için count u 0 olarak başlattık. count değeri 5'ten küçük olduğu sürece next metodu count değerini artırarak Some içine sarılmış geçerli değeri döndürecek, count değeri 5 olduğundaysa, yineleyicimiz count değerini artırmayı sona erdirerek None döndürmeye başlayacaktır.

Counter Yineleyicisinin next Metodunu Kullanmak

Iterator özelliğini uyguladığımıza göre artık elimizde bir yineleyicimiz var demektir. Tıpkı Örnek 13-15'te yaptığımız bir vektörden oluşturulan yineleyicide olduğu gibi, aşağıda yer alan Örnek 13-22 de, Counter yapısının yineleme işlevini next metodunu doğrudan çağırarak kullanabileceğimizi gösteren bir test bölümü içerir.

Dosya adı: src/lib.rs

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calling_next_directly() {
        let mut counter = Counter::new();

        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);
    }
}

fn main() {}

Örnek 13-22: next metodu uygulamasının işlevselliğini test etmek

Bu test, counter değişkeninde yeni bir Counter örneği oluşturur. Ardından next metodunu defalarca çağırıp bu yineleyicinin sahip olmasını istediğimiz davranışı uyguladığımızı doğrulayam 1'den 5'e kadar olan değerleri döndürür.

Diğer Iterator Özellik Metodlarını Kullanmak

Artık next metodunu tanımlayarak Iterator özelliğini uyguladığımıza ve hepsinin next metodunun işlevselliğini kullandıklarını bildiğimize göre, bundan böyle standart kitaplıkta tanımlanan tüm Iterator özellik metodlarının varsayılan uygulamalarını kullanabiliriz.

Örnek 13-23'teki testte de gösterildiği gibi, bir Counter örneği tarafından üretilen değerleri almak istediğimizi, bunları ilk değeri atladıktan sonra başka bir Counter örneği tarafından üretilen değerlerle eşleştirdiğimizi, her çifti birbiriyle çarptığımızı ve elde edilen değerlerin sadece 3'e bölünebilenlerini alarak birbiriyle topladığımız bir örneği düşünün:

Dosya adı: src/lib.rs

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calling_next_directly() {
        let mut counter = Counter::new();

        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);
    }

    #[test]
    fn using_other_iterator_trait_methods() {
        let sum: u32 = Counter::new()
            .zip(Counter::new().skip(1))
            .map(|(a, b)| a * b)
            .filter(|x| x % 3 == 0)
            .sum();
        assert_eq!(18, sum);
    }
}

fn main() {}

Örnek 13-23: Counter yineleyicisinde farklı Iterator özellik metodlarını kullanmak

zip metodu, girdi yineleyicilerinden herhangi birisinin None döndürmesi halinde None varyantını döndüreceğinden, zip ifadesinin yalnızca dört çift oluşturabildiğini ve teorik olarak beşinci çift (5, None) olacağından, hiçbir zaman üretilmeyeceğini aklınızdan çıkarmayın.

Standart kitaplık next metodunu çağıran diğer yöntemler için varsayılan uygulamaları sağladığından, next metodunun nasıl çalıştığını belirledikten sonra metod çağrılarının tamamını kullanmamız mümkündür.

I/O Projemizi Geliştirmek

Performans Karşılaştırması: Döngüler vs. Yineleyiciler

Cargo ve Crates.io Hakkında Daha Fazla Bilgi

Şimdiye kadar kodumuzu oluşturmak, çalıştırmak ve test etmek için Cargo'nun yalnızca en temel özelliklerini kullandık, ancak Cargo ile çok daha fazlası yapılabilir. Bu bölümde, aşağıdaki işlemlerin nasıl yapılacağını gösterebilmek amacıyla Cargo'nun gelişmiş özelliklerinden bazılarını tartışacağız:

  • Sürüm profilleri aracılığıyla derlemenin özelleştirilmesi
  • Kütüphanelerin crates.io'da yayınlanması
  • Büyük projelerin çalışma alanları aracılığıyla düzenlenmesi
  • crates.io'da yer alan ikili dosyaları cargo install yardımıyla kurmak
  • Özel komutları kullanarak Cargo'yu genişletmek

Cargo'nun yetenekleri bu bölümde ele aldığımızdan konulardan çok daha fazlasını barındırdığından, tüm özellikleri değerlendirebilmek için bu bağlantıda yer alan belgelerine başvurabilirsiniz.

Sürüm Profilleriyle Derlemeleri Özelleştirmek

Rust'ın sürüm profilleri, kodunu derleme aşamasına getirmiş olan programcıya, çeşitli seçenekler üzerinde daha fazla denetim izni veren, değişik yapılandırmalara sahip önceden tanımlanmış ve özelleştirilebilir profillerdir. Her profil diğerlerinden bağımsız olarak yapılandırılır.

Cargo'nun iki ana profili vardır: Bunlardan dev seçeneği, programın cargo build komutuyla işletilmesi sırasında kullanılan profil, release seçeneğiyse cargo build --release komutuyla işletilirken kullanılan profildir. Geliştirici profili (dev), geliştirme için iyileştirilen donanımlara sahipken, sürüm profili (release), sürüm derlemeleri için iyileştirilen donanımlarla gelir.

Bu profil adları size sürüm çıktılarınızdan tanıdık geliyor olabilir:


$ cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
$ cargo build --release
    Finished release [optimized] target(s) in 0.0s

Yukarıdaki derleme çıktısında gösterilen dev ve release ifadeleri, derleyicinin farklı profiller kullandığını gösterir.

Eğer projenizin Cargo.toml dosyasında [profil.*] bölümü yoksa Cargo geçerli olan profillerin her biri için varsayılan ayarlara sahip demektir. Özelleştirmek istediğiniz herhangi bir profili [profil.*] bölüm başlığının altına ekleyerek, varsayılan ayarların alt kümelerini geçersiz kılabilirsiniz. Örneğin, dev ve release profillerinin opt-level (tercih düzeyi) ayarları için varsayılan değerleri şunlardır:

Dosya adı: Cargo.toml

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

0-3 aralığındaki Opt-level ayarı, Rust'ın kodunuza uygulayacağı optimizasyon değerini kontrol eder. Henüz geliştirme aşamasındaysanız ve kodunuzu sık sık derliyorsanız, daha fazla optimizasyon uygulamak derleme süresini uzatacağından, kodunuz yavaş çalışsa bile derleme sürenizin hızlı olmasını istersiniz. O nedenle dev profili için opt-level düzeyi varsayılanı 0 olarak atanmıştır. Oysa sürüm modunda, programınızı bir kere derleyeceğiniz ve sonrasında çok kere çalıştıracağınızdan, kodunuzu yayınlamaya hazır olduğunuzda derleme süresi için daha fazla zaman ayırmayı tercih edeceksiniz. Yani daha hızlı çalışan kodun üretim aşaması daha fazla zaman gerektireceğinden sürüm modu için varsayılan profil opt-level = 3 düzeyine ayarlanmıştır.

Varsayılan ayarların herhangi birini projenizin Cargo.toml dosyasına farklı bir değer ekleyerek geçersiz kılabilirsiniz. Örneğin, dev profilinin optimizasyon düzeyini 1 olarak ayarlamak istiyorsanız, projenizin Cargo.toml dosyasına bu iki satırı eklemeniz yeterlidir:

Dosya adı: Cargo.toml


[profile.dev]
opt-level = 1

Bu kod varsayılan opt-level = 0 düzey ayarını geçersiz kılacağından, cargo build komutunu çalıştırdığımızda, Cargo tarafından dev profili için varsayılan değerlerle birlikte, opt-level düzeyinde gerçekleştirdiğimiz özelleştirme seviyesi değerlendirilecektir. Bununla birlikte derleyici, opt-level düzeyini 1 olarak ayarladığımızdan, sürüm profilinde olduğu kadar olmasa da, varsayılandan daha fazla iyileştirme uygular.

Profillerin yapılandırma seçenekleri ve varsayılan değerlerinin tam listesi için Cargo belgeleri'ni inceleyebilirsiniz.

Bir Sandığı Crates.io Üzerinde Yayınlamak

Önceki bölümlerde gerçekleştirdiğimiz bazı örnek projeler, çalışabilmek için crates.io'daki bazı paketlere bağımlı olduklarından, bu paketleri projelerimize dahil etmeyi öğrenmiştik. Halbuki sizler de kendi paketlerinizi yayınlayarak kodlarınızı başkalarıyla paylaşabilirsiniz. Crates.io sitesinde bulunan sandık kayıt defteri, paketlerinizin kaynak kodunu dağıtacağından, öncelikle projenizin açık kaynak kodunu barındırmak zorundadır.

Rust ve Cargo, yayınlanan paketleri başka geliştiricilerin kolaylıkla bulup kullanabilmelerini sağlayan özelliklere sahiptir. Az sonra bu özelliklerin bazılarından bahsedecek ve ardından bir paketin nasıl yayınlanacağını anlatacağız.

Kullanışlı Belgeleme Yorumları Oluşturmak

Paketlerinizin doğru biçimde belgelenmesi, bu paketlerin başka kullanıcılar tarafından nasıl ve ne zaman kullanılabileceğine ışık tutacağından, belgeleme sürecine zaman ayırmak önemlidir. Bölüm 3'te, Rust kodlarını iki eğik çizgi // kullanarak nasıl yorumlayacağımızı tartışmıştık. Bu normal yorumların yanısıra Rust, belgeleme yorumları olarak bilinen ve kod içinde yapılan açıklamaları, HTML belgelerine çevirmeye yarayan özel bir yorumlama biçimine sahiptir. Bu HTML belgeleri, sandığınızın nasıl uygulandığını anlatmaktan ziyade, nasıl kullanılacağını öğrenmek isteyen programcılara yol gösteren genel API öğelerinin belgelenmiş içeriğinden oluşur.

Belgeledikleri öğeden hemen önce yerleştirilen ve iki yerine /// üç eğik çizgi ile ifade edilen belgeleme yorumları, metni biçimlendirmek için Markdown gösterimini destekler. Örnek 14-1, sandigim adlı sandıkta yer alan bir_ekle işlevi için belgeleme yorumlarını göstermektedir:

Dosya adı: src/lib.rs


/// Kendisine iletilen sayıya bir ekler
///
/// # Örnekler
///
/// ```
/// let deger = 5;
/// let yanit = sandigim::bir_ekle(deger);
///
/// assert_eq!(6, yanit);
/// ```

pub fn bir_ekle(x: i32) -> i32 {
    x + 1
}

Örnek 14-1: Bir işlevin belgelenmesi

Örnekteki bir_ekle işlevinin görevini anlatıp, Örnekler etiketli bir bölüm başlatarak bir_ekle işlevinin nasıl kullanılacağını gösteren kodlarla başlıyoruz. Bu işlemleri tamamladıktan sonra cargo doc komutunu çalıştırarak bu yorum satırlarının işlenmesiyle oluşturulan bir HTML belgesine sahip oluruz. Bu komutla oluşturulan HTML belgeleri rustdoc araç seti çalıştırılarak target/doc dizinine yerleştirilecektir.

Biraz daha rahatlık sağlayan cargo doc --open komutu ise, hem sandığınıza ait tüm bağımlılıkların HTML belgelerini oluşturacak, hem de oluşturduğu belgeleri web tarayıcınızda açarak kullanımınıza sunacaktır. Şimdi bir_ekle işlevine giderek, Resim 14-1'de gösterilen belgeleme yorumlarının metne nasıl dönüştürüldüğünü inceleyebilirsiniz:

`my_crate` sandığında `bir_ekle` işlevi için oluşturulmuş HTML belgeleri

Resim 14-1: bir_ekle işlevinin HTML belgeleri

Yaygın Olarak Kullanılan Bölümler

HTML belgesinde # Örnekler başlıklı bir bölüm oluşturabilmek için Örnek 14-1'de # Örnek şeklinde bir Markdown başlığı sözdizimi kullandık. Pek çok sandık yazarının belgelerinde yaygın olarak kullandığı bazı bölümler ise aşağıda sıralanmaktadır:

  • Panikler: Belgelenen işlevin panik üretebileceği senaryolar. İşlevin çağrıldığı programlarda panik üretmesi istenmiyorsa, kullanıcıların bu senaryoların gerçekleşebileceği durumlarda işlevi çağırmadıklarından emin olunmalıdır.
  • Hatalar: İşlev bir Result türü döndürdüğünde, oluşması muhtemel hata çeşitlerinin ve bu hataların döndürülme neden ve koşullarının tanımlanması, işlevi çağıran tarafların farklı türden hataları farklı şekillerde işlemelerini sağlayacak şekilde kod üretmelerine yardımcı olabilir.
  • Güvenlik: Eğer işlev çağrısı unsafe yani emniyetsiz bir çağrı ise (Rust'ın emniyetsiz kullanım seçeneğini Bölüm 19'da tartışacağız), işlevin güvensiz olma nedenlerini açıklayan ve çağıran tarafların desteklemesi gereken değişmezleri kapsayan bir bölüm olmalıdır.

Çoğu belgelendirme çalışmasında bu bölümlerin her birinin yorumlanmasına ihtiyaç duyulmaz. Bununla birlikte bu bölümler kodlarınızı çağıran tarafların bilmek isteyeceği yönleri hatırlamanız amacıyla tavsiye edilen bir kontrol listesidir.

Test Amaçlı Belgeleme Yorumları

Belgeleme yorumlarınıza örnek kod blokları eklemek, kütüphanenizin nasıl kullanılacağını göstermenize yardımcı olabileceği gibi ek bir avantaj olarak cargo test komutu çalıştırıldığında kod örneklerinizin test edilmesine olanak sağlar. Hiçbir şey test edilebilecek örnekler içeren belgelerden daha iyi olamaz. Ancak, belgeleme sonrası gerçekleşen kod değişiklikleri yüzünden işe yaramayan örneklerden daha kötü bir şey de yoktur. Örnek 14-1'deki bir_ekle işlevi için oluşturduğumuz belgeleme koduna cargo test komutunu uyguladığımızda, aşağıdakine benzer bir test sonucu göreceğiz:


   Doc-tests sandigim

running 1 test
test src/lib.rs - bir_ekle (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Bu aşamada işlev ya da örnek değiştirilecek olursa, örnekteki assert_eq! ifadesi panik üreteceğinden cargo test komutu tekrar çalıştırıldığında, test sürecinin örnek ve kod bölümlerinin uyumsuzluğunu fark ettiğini gözlemleyeceğiz.

İçerilen Öğelerin Yorumlanması

Bir başka belgeleme biçimi olan //! ise, yorum satırlarının hemen altına eklenenen öğeleri belgelemek yerine, yorumun ait olduğu, yani içerildiği öğeyi belgelemek için kullanılır. Bu tarz yorum satırlarını genellikle sandık veya modülün tamamını bir bütün olarak belgelemek amacıyla, kök dosyasının içinde (kural gereği src/lib.src) ya da bir modül içerisinde kullanırız.

Örnek 14-2'de gösterildiği gibi, eğer daha önce oluşturduğumuz ve bir_ekle işlevini içeren sandigim için, bu sandığın amacını açıklayan belgeler eklemek istiyorsak, bunları src/lib.rs dosyasının en başına //! işaretini kullanarak eklememiz gerekir.

Dosya adı: src/lib.rs


//! # Sandigim
//!
//! `Sandigim`, bazı hesaplamaların daha kolay yapılmasını
//! sağlayan araçlar koleksiyonudur.

/// Kendisine iletilen rakama 1 ekler.
// --snip--
#///
#/// # Örnekler
#///
#/// ```
#/// let rakam = 5;
#/// let yanit = sandigim::bir_ekle(rakam);
#///
#/// assert_eq!(6, yanit);
#/// ```
#pub fn add_one(x: i32) -> i32 {
#    x + 1
#}

Örnek 14-2: Bir bütün olarak Sandigim belgeleri

//! işaretiyle başlayan son satırın altında herhangi bir kod satırının bulunmadığına ve bir satırın boş bırakılmış olduğuna dikkat edin! Bunun sebebi, içerilen belge yorumlarını /// yerine, //! işaretiyle başlattığımızdan sonraki satırlarda bulunan öğeler yerine, işaretin bulunduğu satırdaki öğelerin belgelenecek olmasıdır. Bu durumda bu yorumu içeren öğe, sandık kökümüz olan src/lib.rs dosyası olacağından, bu yorumlar da sandığın tamamı için yapılan açıklamaları içerecektir.

Eğer cargo doc --open komutunu çalıştırırsak, işaretlemiş olduğumuz bu yorum satırları, tıpkı Şekil 14-2'de gösterildiği gibi sandigim belgesinin ön sayfasında, sandıktaki genel öğeler listesinin üstünde görüntülenecektir:

Sandığın tamamını içeren yorumlarla oluşturulmuş HTML belgeleri

Resim 14-2: Sandigim'ın tamamını içeren yorumlarla oluşturulmuş HTML belgeleri

Öğelerdeki belge yorumları, özellikle sandık ve modülleri tanımlamak için kullanışlıdır. Bu yorumları, paketlerinizi kullanacak olan kişilerin paket düzeninizi anlamalarına yardımcı olmak ve paket kapsamının genel amacını açıklamak için kullanmanız önemlidir.

Uygun Bir Genel API'yi pub use ile Dışa Aktarmak

Bölüm 7'de kodlarımız, mod anahtar kelimesini kullanarak modüller halinde nasıl düzenleyeceğinizi, pub anahtar sözcüğüyle öğelerin nasıl genelleştirileceğini ve use anahtar kelimesiyle de öğelerin kapsama nasıl dahil edileceğini incelemiştik. Ancak, bir sandığın geliştirilme sürecinde sizin için anlamlı olan organizasyon yapısı, kullanıcılarınız için çok uygun olmayabilir. Sandığınızı çok katmanlı ve hiyerarşik bir yapıda düzenlediğinizde, bu hiyerarşinin alt katmanlarında tanımlanmış bir türü kullanmak isteyen kişiler, bu türe erişmekte sorun yaşayabilirler. Hem ayrıca bir türe use sandigim::KullanisliBirTur; şeklinde bir söz dizimiyle ulaşmak yerine, sandigim::bir_modul::baska_bir_modul::KullanisliBirTur; şeklinde bir söz dizimiyle ulaşmak oldukça rahatsız edici olabilir.

Bir sandık yayınlarken herkese açık olarak tasarlanmış olan API'nizin yapısı oldukça önemlidir. Sandığınızı kullanan kişiler bu yapıya sizin kadar aşina olmadıklarından, sandığınız büyüyüp karmaşık bir modüller hiyerarşisine dönüştüğünde, kullanmak istedikleri API parçalarına ulaşmakta zorluk çekebilirler.

İyi haber şu ki, eğer organizasyon yapınız başkaları tarafından farklı kütüphaneler ile kullanılamayacak gibiyse, API hiyerarşisini veya tasarımını baştan sona yeniden düzenlemek yerine, pub use anahtar kelimesini kullanarak, bu yapının genel kullanıma uygun bir sürümünü tüm öğeleriyle birlikte yeniden ihraç edebilirsiniz. Yeniden ihraç işleminde, bir konumda bulunan genel bir öğe yerinden alınarak, sanki başka bir yerde ve başka bir konumda tanımlanmış gibi herkese açık hale getirilir.

Örnek 14-3'te de görüleceği gibi, sanatsal kavramları modellemek için sanat adında bir kütüphane tasarladığımızı varsayalım. Ve bu kütüphanenin içinde BirincilRenk ve IkincilRenk olarak isimlendirilmiş iki sıralamadan (enum) oluşan turler modülü ve karisim adında bir işlev içeren araclar modülü bulunsun:

Dosya adı: src/lib.rs

//! # Sanat
//!
//! Sanatsal kavramları modellemek için bir kütüphane.

pub mod turler {
    /// RYB renk modeline göre ana renkler.
    pub enum BirincilRenk {
        Kizil,
        Sari,
        Mavi,
    }

    /// RYB renk modeline göre ikincil renkler.
    pub enum IkincilRenk {
        Portakal,
        Yesil,
        Mor,
    }
}

pub mod araclar {
    use crate::turler::*;

    /// İkincil bir renk oluşturmak için iki ana rengi
    /// eşit miktarda birleştirir.
    pub fn karisim(c1: BirincilRenk, c2: BirincilRenk) -> IkincilRenk {
        // --snip--
#        IkincilRenk::Portakal
#    }
#}

fn main() {}

Örnek 14-3: turler ve araclar modülleri halinde düzenlenmiş öğeler içeren bir sanat kütüphanesi

Resim 14-3, Bu sandık içincargo doc tarafından üretilen belgenin ön yüzünü göstermektedir:

`turler` ve `araclar` modüllerini örnekleyen `sanat` sandığı için oluşturulmuş belgeler

Resim 14-3: turler ve araclar modüllerini örnekleyen sanat sandığının ön yüzü

Belgenin ön sayfasında BirincilRenk ve IkincilRenk türleriyle karisim işlevinin listelenmediğine dikkat edin. Onların görüntülenebilmesi için turler ve araclar bağlantılarının açılması gerekir.

Bu kütüphaneye bağımlı olan başka bir sandığın, halihazırda tanımlanmış olan sanat modül yapısına ait öğeleri kendi kapsamına alabilmesi için use ifadesini kullanması gerekir. Örnek 14-4, sanat sandığındaki BirincilRenk ve karisim öğelerini kullanan başka bir sandık örneğini göstermektedir:

Dosya adı: src/lib.rs


use sanat::turler::BirincilRenk;
use sanat::araclar::karisim;

fn main() {
    let kizil = BirincilRenk::Kizil;
    let sari = BirincilRenk::Sari;
    karistir(kizil, sari);
}

Örnek 14-4: İç yapısı dışa aktarılan sanat sandığının öğelerini kullanan başka bir sandık

Örnek 14-4'te yer alan sanat sandığını kullanan kodun programcısı, BirincilRenk türünün turler modülünde ve karisim işlevinin de araclar modülünde olduğunu anlayabilmelidir. Sandığın modül yapısı, sanat sandığını geliştiren programcılardan çok, bu sandığı kullanan programcılar için önemlidir. Sandığın parçalarını turler ve sanat modülleri olarak düzenleyen iç yapı, bu sandığın nasıl kullanılacağını öğrenmek isteyenler için herhangi bir yararlı bilgi içermediği gibi, sanat sandığı modül yapısının yarattığı karmaşa, use ifadelerinde modül adlarını belirtmek isteyen kullanıcıların nereye bakacaklarını karıştırmalarına neden olacağından kullanışsız bir yapıdır.

Herkesin kullanacağı bu API'nin sorunlu iç düzenlemesini pub use kullanarak kaldırıp, öğeleri en üst düzeyde yeniden dışa aktarabilmek için, Örnek 14-3'te yer alan sanat sandığının kodlarını, Örnek 14-5'te gösterildiği şekilde yeniden düzenleyebiliriz.

Dosya adı: src/lib.rs


//! # Sanat
//!
//! Sanatsal kavramları modellemek için bir kütüphane.

pub use self::araclar::karisim;
pub use self::turler::BirincilRenk;
pub use self::turler::IkincilRenk;

pub mod turler {
    // --snip--
#    /// RYB renk modeline göre ana renkler.
#    pub enum BirincilRenk {
#        Kizil,
#        Sari,
#        Mavi,
#    }
#
#    /// RYB renk modeline göre ikincil renkler.
#    pub enum IkincilRenk {
#        Portakal,
#        Yesil,
#        Mor,
#    }
}

pub mod araclar {
    // --snip--
#    use crate::turler::*;
#
#    /// İkincil bir renk oluşturmak için iki ana rengi
#    /// eşit miktarda birleştirir.
#    pub fn karisim(c1: BirincilRenk, c2: BirincilRenk) -> IkincilRenk {
#        IkincilRenk::Portakal
#    }
}
#
#fn main() {}

Örnek 14-5: Öğeleri yeniden dışa aktarmak için pub use ifadesi eklemek

Şekil 14-4'te görebileceğiniz gibi cargo doc komutunun bu sandık için oluşturduğu API belgeleri, dışa aktarımları yeniden ön sayfada listeleyerek bağlayacak, BirincilRenk ve IkincilRenk türleriyle karisim işlevinin bulunmasını oldukça kolaylaştıracaktır.

`sanat` sandığının yeniden dışa aktarımıyla oluşan belgelerin ön sayfası

Resim 14-4: Yeniden dışa aktarımı örnekleyen sanat sandığının ön yüzü

Artk sanat sandığını kullanmak isteyen programcılar, ister hâlâ kullanılmaya uygun durumdaki Örnek 14-3 ve Örnek 14-4'ün iç yapılarını inceleyerek kodlarına bunları dahil edebilirler, isterlerse Örnek 14-5 ve 14-6'da yenilenerek kullanıma daha uygun hale getirilen yapıyı tercih edebilirler.

Dosya adı: src/main.rs


use sanat::karisim;
use sanat::BirincilRenk;

fn main() {
    // --snip--
#    let kizil = BirincilRenk::Kizil;
#    let sari = BirincilRenk::Sari;
#    karisim(kizil, sari);
}

Örnek 14-6: sanat sandığının yeniden dışa aktarılan öğelerini kullanan bir program

İç içe geçmiş çok sayıda modülü, türleri pub use ifadesiyle en üst düzeyde yeniden dışa aktarmak, sandığı kullanacak kişilerin deneyimlerinde önemli farklar yaratabilir.

Kullanışlı bir genel API tasarımı oluşturmak bilimden çok sanat olarak kabul edildiğinden, kullanıcılarınız için en iyi çalışacak uygun bir düzenleme için defalarca tekrar yapmanız gerekebilir. Bununla birlikte pub use kullanımını seçmek, sandığınızın iç düzenlemesinde size esneklik sağlarken, bu iç düzeni kullanıcılarınıza sunduğunuz arayüzden ayırır. Kurduğunuz bazı sandıkların iç düzenlemelerinin genel API katmanlarına göre farklarını incelemek için bu sandıkların kodlarına bakmanız öğretici olacaktır.

Crates.io Hesabı Oluşturmak

Herhangi bir sandığı yayınlayabilmeniz için öncelikle crates.io üzerinde bir hesap oluşturmanız ve bir API anahtarı almanız gerekir. Bunun için (Her ne kadar gelecekte siteye başka yöntemlerle üye olunması planlanmış olsa da şu an için yalnızca GitHub hesaplarımız ile giriş kabul edildiğinden) crates.io adresini ziyaret ederek GitHub hesabınız ile giriş yapın. Ardından, https://crates.io/me/ adresindeki hesap ayarlarınızı gözden geçirerek API anahtarınızı alın. Aldığınız bu API anahtarını cargo login komutuna ekleyerek tıpkı aşağıda örneklendiği gibi çalıştırın:


$ cargo login abcdefghijklmnopqrstuvwxyz012345

Bu komut API anahtarınızı Cargo'ya bildirecek ve yerel olarak onu ~/.cargo/credentials içinde depolayacaktır. Anahtarınızın size özel olduğunu, gizli kalması ve kimseyle paylaşılmaması gerektiğini unutmayın. Eğer herhangi bir sebeple anahtarınızı birileriyle paylaşmak zorunda kalırsanız, eskisini derhal iptal ederek yeni bir anahtar oluşturun.

Yeni Oluşturulmuş Sandığa Meta Veri Eklemek

Artık hesabınızı oluşturduğunuza göre, yayınlamak istediğiniz bir sandığınız olduğunu düşünebiliriz. Ancak sandığınızı yayınlamadan önce, Cargo.toml dosyasının [package] bölümüne sandığınıza ait meta veriler eklemeniz gerekir.

Bir sandık üzerinde yerel olarak çalışırken onu istediğiniz gibi adlandırabilirsiniz. Ancak sandığınız yayın aşamasına geldiğinde benzersiz bir isme ihtiyacı olacak. crates.io'daki sandık isimlerinde öncelik ilk gelene verildiğinden, bir isim bir sandığa tahsis edildikten sonra başka bir sandığa tahsis edilemez. Başka bir ifadeyle, sandık adı bir kez alındığında aynı isimde başka bir sandık yayınlanamaz. O yüzden sandığınızı crates.io üzerinde yayınlanmadan önce, sandık adınızı kullanan başka bir sandık olup olmadığını araştırmalısınız. Eğer sandık adınız halihazırda başka bir sandık tarafından kullanılmakta ise, yenileyeceğiniz sandık adını, paketinizin Cargo.toml dosyasında bulunan [package] bölümüne, isim alanıyla birlikte aşağıda gösterildiği gibi girmeniz gerekir.

Dosya adı: Cargo.toml


[package]
name = "tahmin_oyunu"

Her ne kadar benzersiz bir ad seçmiş olsanız bile, sandığı yayınlamak için cargo publish komutunu çalıştırdığınızda aşağıdakine benzer bir uyarı ve hata alabilirsiniz:


$ cargo publish
    Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
--snip--
error: api errors (status 200 OK): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata


Bunun sebebi, sandığınızı kullanmak isteyecek programcılar için hazırlamanız gereken, sandığınızın neler yaptığını ve hangi koşullar altında kullanılabileceğini düzenleyen açıklama ve lisans bilgileri gibi önemli detayları atlamış olmanızdır. Bu hatayı düzeltmek için gerekli olan bilgileri paketinizin Cargo.toml dosyasına işlemeniz gerekir.

Girdiğiniz açıklamalar arama sonuçlarında görüntüleneceğinden, en azından bir iki cümlelik açıklama eklemeniz yerinde olur. Lisans alanı içinse bir license tanımlayıcı değeri vermeniz gereklidir. Linux Vakfı'nın Yazılım Paketi veri değişimi (SPDX), bu alan için kullanabileceğiniz tanımlayıcıları listeler. Örneğin, sandığınızı MIT Lisansı ile lisansladığınızı belirtmek için MIT tanımlayıcısını eklemeniz gerekir:

Dosya adı: Cargo.toml


[package]
name = "tahmin_oyunu"
license = "MIT"

SPDX'te listelenmemiş bir lisans kullanmak istiyorsanız, söz konusu lisansın metnini bir dosyaya yerleştirmeniz, dosyayı projenize eklemeniz ve ardından license alanındaki tanımı license-file şeklinde dosya adını tanımlayacak şekilde belirtmeniz gerekmektedir.

Projeniz için hangi lisansın daha uygun olacağına dair rehberlik bu kitabın kapsamı dışındadır. Rust topluluğunun pek çok üyesi projelerini, Rust'ın tercih ettiği gibi MIT OR Apache-2.0 olarak çifte lisans kullanarak sunar. Bu uygulama biçimi projenizi OR ekiyle birden fazla lisansa sahip olacak şekilde lisans tanımlayıcısıyla ilişkilendirebileceğinizi gösterir.

Benzersiz bir isim seçtiğiniz sandığınıza; yazar adı, sürüm bilgisi, paket açıklaması ve lisans bilgileri eklendikten sonra, yayına hazır hale gelen projenizin Cargo.toml dosyası aşağıdaki dosya gibi görünecektir:

Dosya adı: Cargo.toml


[package]
name = "tahmin_oyunu"
version = "0.1.0"
authors = ["Rust Dili <rustdili@gmail.com>"]
edition = "2018"
description = "Bilgisayarın seçtiği sayıyı tahmin ederken eğleneceğiniz keyifli bir oyun."
license = "MIT OR Apache-2.0"

[dependencies]

Sandıklarınızın kolaylıkla fark edilip kullanabilmesi için değerlendirebileceğiniz meta verilere Cargo Belgeleri üzerinden kolaylıkla ulaşabilirisiniz.

Bir sandığı Crates.io'da Yayınlamak

Artık bir hesabınız, API anahtarınız, benzersiz ada sahip bir sandığınız olduğuna ve bu sandığa gerekli meta verileri eklediğinize göre sandığızı yayınlamaya hazırsınız demektir. Bir sandığı yayınlamak demek, sandığınızın belirli bir sürümünü başka kullanıcılar için crates.io sitesine yüklemek anlamına gelir.

Crates.io'nun asıl hedeflerinden biri yayınlanan sandıklara bağımlı tüm projelerin çalışmaya devam edebilmesi için kalıcı bir kod arşivi oluşturmaktır. Bu nedenle bir sandık yayınlandıktan sonra, yayınlanan sürümün üzerinde ekleme, düzenleme yahut silme gibi işlemler artık yapılamayacağından, sandık yayınlarken dikkatli olunması gerekir. Sürüm silme işlemine izin vermek bu hedefi imkansızlaştıracağından, sürüm silmek yerine sandığın başka sürümlerinin yayınlaması yeğlenir. O nedenle bir sandığa ait sürümlerin yayın sınırı yoktur.

Şimdi cargo publish komutunu tekrar çalıştırdığınızda paketinizin başarıyla yayınlandığını göreceksiniz:


$ cargo publish
    Updating crates.io index
   Packaging tahmin_oyunu v0.1.0 (file:///projem/tahmin_oyunu)
   Verifying tahmin_oyunu v0.1.0 (file:///projem/tahmin_oyunu)
   Compiling tahmin_oyunu v0.1.0
(file:///projem/tahmin_oyunu/target/package/tahmin_oyunu-0.1.0)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
   Uploading tahmin_oyunu v0.1.0 (file:///projem/tahmin_oyunu)

Tebrikler! Artık kodunuzu Rust topluluğuyla paylaştığınıza göre, herkes sizin sandığınızı kendi projesine bağımlılık olarak kolayca ekleyebilir.

Mevcut Sandığın Yeni Sürümünü Yayınlamak

Daha önce yayınladığınız bir sandık üzerinde tüm kullanıcıları etkileyebilecek değişiklikler yaptığınızda ya da sandığınızın yeni sürümünü yayınlamak istediğinizde, Cargo.toml dosyasında belirtilen sürüm değerini değiştirerek yeniden yayınlayabilirsiniz. Yaptığınız değişiklik türlerine bağlı olarak, sonraki sürümün numarasına karar verirken Anlamsal Sürüm Oluşturma Kuralları sitesini kullanabilirsiniz. Yeni sürümünüzü, düzenlemelerinizi gerçekleştirdikten sonra cargo publish komutunu kullanarak yayınlayabilirsiniz.

Sürümleri Crates.io'dan cargo yank Komutu Kullanarak Kaldırmak

Yayımlanmış bir sandığın önceki sürümlerini kaldıramıyor olsanız bile, bir sebepten sandık sürümünüz bozulmuşsa, yeni projelerin bu sürümleri kullanmasını ve bağımlılık olarak eklemesini engelleyebilirsiniz. Cargo'nun böyle durumlarda kullanılan yank komutu ilgili sürümün geri çekilmesini sağlar.

Bir sürümün geri çekilmesi, yeni projelerin bu sürüme bağlanmasını önlerken, halihazırda kendisine bağımlı olan projelerin bu sürümü indirmesine ve bağımlı olarak çalışmasına izin verir. Temel olarak yank, yani geri çekme işlemi, Cargo.lock dosyasına işlenmiş projelerin bozulmadan kullanılmaya devam edeileceğini, ama yeni bağımlılık taleplerinde bu sürümün kullanılmasına izin verilmeyeceği anlamına gelmektedir.

Geri çekilmek istenen sandık sürümü için cargo yank komutunun aşağıda örneğe benzer biçimde kullanılması gerekir:


$ cargo yank --vers 1.0.1

Bu geri çekme işlemini cargo yank komutuna --undo ekleyerek geçersiz hale getirebilir ve projelerin bu sürüme yeniden bağlanmasına izin verebilirsiniz:


$ cargo yank --vers 1.0.1 --undo

Geri çekme işlemi halihazırda var olan kodları silmez. Eğer projenize yanlışlıkla eklediğiniz bazı özel veya sır olarak kalması gereken kodlar varsa ve bu kodları yank kullanarak silmek istiyorsanız, bunu yapmanız mümkün olamayacağından, sürümünüzü yayınlamadan önce bu kodları sıfırlamanız veya kaldırmanız gerekir.

Cargo Çalışma Alanları

Crates.io Üzerindeki İkili Sandıkları cargo install Komutuyla Yüklemek

Özel Komutlarla Cargo Olanaklarını Genişletmek

Akıllı İşaretçiler

Heap Üzerindeki Verilere İşaret Etmek İçin Box<T> Kullanmak

Deref Özelliği ile Akıllı İşaretçilere Normal Referanslar Gibi Davranmak

Drop Özelliği ile Kodu Temizlik Amaçlı Çalıştırmak

Rc<T>, Referans Sayılı Akıllı İşaretçi

RefCell<T> ve İç Değişkenlik Modeli

Referans Çevrimleri Bellek Sızıntısına Yol Açabilir

Korkusuz Eşzamanlılık

Eşzamanlı Kod Çalıştırmak İçin İşlikleri Kullanmak

İşlikler Arasında Veri Aktarmak Amacıyla Mesajlaşma

Durum Paylaşımlı Eşzamanlılık

Sync and Send Özellikleri ile Genişletilebilir Eşzamanlılık

Rust'ın Nesne Yönelimli Programlama Özellikleri

Nesne Yönelimli Dillerin Özellikleri

Farklı Türden Değerlere İzin Veren Özellik Nesnelerini Kullanmak

Nesne Yönelimli Tasarım Kalıbı Uygulamak

Örüntü ve Eşleme

Örüntüler Her Yerde Kullanılabilir

Çürütülebilirlik: Bir Örüntünün Eşleşmeme İhtimali

Örüntü Sözdizimi

Gelişmiş Özellikler

Güvenli olmayan Rust Kodları

Gelişmiş Özellikler

Gelişmiş Türler

Gelişmiş İşlev ve Kapamalar

Makrolar

Final Projesi: Çok İşlikli Web Sunucusu Oluşturmak

Tek İşlikli Bir Web Sunucusu Oluşturmak

Tek İşlikli Sunucumuzu Çok İşlikli Bir Sunucuya Dönüştürmek

Sorunsuzca Kapatmak ve Temizlik

Ekler

Aşağıdaki bölümler Rust yolculuğunuzda faydalı olabilecek referans materyalleri içermektedir.

Ek A: Anahtar Kelimeler

Ek B: İşleçler ve Semboller

Ek C: Türetilebilir Özellikler

Ek D: Faydalı Geliştirme Araçları

Ek E: Sürümler

Ek F: Kitabın Çevirileri

Ek G: Rust Nasıl “Nightly Rust” Yapılır?