Rust Programlama Dili

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

Metnin bu sürümü, Rust 1.57 (2021-12-02'de yayınlandı) veya sonraki bir sürümünü kullandığınızı varsayar. Rust'ı yüklemek veya güncellemek için 1. Bölümdeki Kurulum bölümünü ziyaret edebilirsiniz.

Bu kitabın orijinal dildeki çevrimiçi HTML formatı https://doc.rust-lang.org/stable/book/ adresinde, Türkçe sürümü ise Türkçe HTML 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ın İngilizce yazılmış ve ciltsiz, e-kitap baskısını 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 bu baskısı No Starch Press'teki Rust Programlama Dili Kitabının İngilizce baskısı ile aynıdı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. Üst düzey ergonomi ve düşük seviyeli kontrol programlama dili tasarımlarında bir çelişki gibi görünüyor olsa da Rust bu çelişkiye meydan okur. Rust, 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 sistem programlama dünyası için ç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. Sıfır maliyetli soyutlamalar, elle yazılmış kodlar gibi hızlı biçimde düşük seviyeli kodlara derlenebilen üst düzey özellikler için çabalayan Rust, güvenle çalışan kodları hızlı çalışan kodlar 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

Halihazırda bu kitabın içeriği, okuyucusunun herhangi bir programlama dilinde kod yazdığı kabulüne dayanarak hazırlandığından, farklı programlama geçmişlerine sahip geniş bir izleyici kitlesine uygun olarak hazırlanmıştır. 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ı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, diğer bölümler ise kavramsal kısımları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 kullanılacağı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.

Dilerseniz Rust'ın diğer programlama dillerindeki benzer özelliklerini tartıştığımız 3. Bölümü atlayarak, doğrudan Rust'ın mülkiyet sistemini anlatan kitabın 4. Bölümüne geçiş yapabilirsiniz. Eğer tüm ayrıntıları öğrenmek isteyen titiz bir öğrenciyseniz, bir sonraki bölüme geçmeden önce proje kısmı olan 2. Bölümü atlayarak 3. Bölüme geçebilir, sonrasında öğ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, enumlar (numaralandırmalar), ö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 enumlardan 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 generics veri türleri, ö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şturarak, önceki bölümlerde öğrendiğimiz çoğu kavramı kullanarak bilgilerimizi pekiştirmeye çalışacağı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.

Bölüm 16'da, eşzamanlı programlamanın çeşitli modellerini inceleyecek ve Rust'ın paralel görevleri çok sayıda iş parçacığına nasıl korkusuzca dağıtmamıza yardım ettiğini konuşacağız.

Bölüm 17, Rust deyimlerini aşina olduğunuz nesne yönelimli programlama ilkeleriyle karşılaştırır.

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.

Bölüm 19, Güvenli olmayan Rust kodları, makrolar, yaşam süreleri, özellikler, türler, işlevler, ve kapamalar hakkında fazladan ayrıntılar 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österimi 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.

FerrisAnlamı
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][kitap]

[kitap]: https://github.com/RustDili/rust-book-tr/tree/main/TURKISH/src üzerinden ulaşabilirsiniz.

Başlarken

Öğrenilecek çok şey olmasına rağmen "Her yolculuk bir başlangıç noktasından başlar" 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 çıktısı basan ilk Rust programını 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'ın hata mesaj ve uyarılarını sürekli iyileştirmesinden dolayı, derleyici çıktıları sürümden sürüme farklılık gösterebilir. 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 ve derlenmiş çıktıları tek bir dosyada birlerştirmek amacıyla kullandığı bir tür bağlayıcıya 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.

macOS için bir C derleyicisini aşağıdaki komutu çalıştırarak alabilirsiniz:

$ xcode-select --install

Linux kullanıcıları ise dağıtım belgelerine uygun olarak GCC veya Clang kurmalıdır. Örneğin, eğer Ubuntu kullanıyorsanız build-essential paketini yükleyebilirsiniz.

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)

Gö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ğinize 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 veya tercih ettiğiniz 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_dunya

Windows CMD içinse şu komutları girin:

> mkdir "%USERPROFILE%\projeler"
> cd /d "%USERPROFILE%\projeler"
> mkdir merhaba_dunya
> cd merhaba_dunya

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

Şimdi yeni bir kaynak dosyası oluşturun ve bunu main.rs olarak isimlendirin. Rust dosyaları daima .rs uzantısıyla sonlanır. Dosyalarınızı birden fazla kelime ile adlandırıyorsanız, bu adları alt_çizgi kullanarak birbirinden ayırın. Örneğin merhabadunya.rs yerine merhaba_dunya.rs'yi teercih edin.

Az önce oluşturduğunuz main.rs dosyasını açarak Örnek 1-1'de yer alan kod satırlarını dosyanıza ekleyin:

Dosya adı: main.rs

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

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

Dosyanızı kaydedip terminal penceresine geri dönün. 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!

Kullandığınız işletim sisteminden bağımsız olarak, 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 Merhaba, dünya! satırını görüyorsanız tebrikler, bu sizin bir Rust programı yazdığınıza gösterir! Bir programcısı olarak aramıza hoş geldiniz.

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. Eğer işleve parametre iletecek olsaydık, bu parametreleri () parantezin içine koymamız gerekirdi.

Ayrıca işlev gövdesinin süslü parantezlerle {} sarıldığıba dikkat edin. Rust'ta işlev gövdeleri bu süslü parantezler içine alınmak zorundadır. Girişi gösteren ilk süslü parantezi, işlev bildirimiyle aynı satıra yerleştirip bildirim ile aralarında bir boşluk bırakmak Rust'ın standart yazım biçimidir.

Rust projelerinde standart yazım biçimine 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 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ı vardır.

İlki: Rust stili girintilerde bir sekme (tab) yerine dört boşluk (space) kullanılır.

İkincisi: println! terimi bir Rust makrosu çağırır. Eğer burada bir işlev çağrısı yapılıyor olsaydı, println! yerine (! olmadan) println yazılmış olacaktı. Rust makrolarını 19. bölümde ayrıntılarıyla inceleyeceğiz. Ancak şimdilik ! işaretininin normal bir işlev çağrısı değil, işlevler ile aynı kurallara uymayan bir makro çağrısı anlamına geldiğini 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.

Ve sonuncusu: Satırın noktalı virgül (;) ile bitiyor olması, bu ifadenin bittiğini ve bir sonrakinin başlamaya hazır olduğunu bildirir. Rust kodlarındaki pek çok satır noktalı virgül ile biter.

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

Az önce oluşturduğunuz yeni programınızın çalışma sürecindeki adımlarını incelelim

Bir Rust programı çalıştırılmadan önce Rust derleyicisi kullanılarak ve rustc komutuna aşağıdaki gibi kaynak dosyası adı verilerek derlenmelidir:

$ rustc main.rs

C veya C++ dillerine aşinaysanız, bu işlemin gcc veya clang ile 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örünürken, Windows PowerShell'de CMD kullanıldığında üç 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 main.rs dosyanız "Merhaba, dünya!" programınızı içeriyorsa terminalinize "Merhaba, dünya!" metni yazdıracaktı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 ahead-of-time compiled (öncesinde derlenmiş) bir dildir. Bu derlenmiş bir Rust programının yürütülebilir dosyasının dağıtılabileceği ve dağıtılan bu dosyanın Rust kurulumuna ihtiyaç duymadan çalıştırılabileceği anlamına gelir. Ancak bir rb, .py, veya .js dosyası dağıttığınızda bu dosyanın kullanılacağı ortamda bir Ruby, Python veya JavaScript uygulamasının yüklü olması gerekir. Bununla birlikte bu dillerden biriyle yazılmış olan bir programı çalıştırabilmek için yalnızca bir komutun kullanılması yeterlidir. Dil tasarımında her şey bir değiş tokuştur.

Her ne kadar basit programların rustc ile derlenmesi yeterliymiş gibi görünse de projeniz büyüdükçe seçeneklerin tümünü yönetmek ve kodun dağıtılmasının kolaylaştırmak isteyeceksiniz. Sonraki bölümde sizi gerçek dünyada daha sık kullanılan ve daha karmaşık Rust programları yazmanıza yardım edecek olacak Cargo aracıyla tanıştıracağız.

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.

Şu ana kadar yazdığımıza benzeyen basit Rust programları herhangi bir kütüphaneye bağımlı değildir. Bu nedenle "Merhaba dünya!" gibi basit bir proje Cargo ile derlendiğinde, sadece Cargo'nun aracının kod derlemeyi yöneten bölümü kullanılır. Yazılan programlar basitten karmaşığa doğru evrildikçe farklı kütüphanelere olan bağımlılıkları artacağından Cargo aracı bu bağımlılıkların yönetilmesinde size büyük kolaylıklar sağlayacaktı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 önerilen resmi yükleyicileri kullandıysanız Rust'la birlikte Cargo aracınız da yüklenmiş olmalıdır. Ancak Rust'ı farklı bir yoldan kurduysanız aşağıdaki kodları terminalinize girerek Cargo'nun sisteminizde kurulu olup olmadığını öğrenebilirsiniz.

$ cargo --version

Çıktınızda bir sürüm numarası görüyorsanız bu, Cargo aracının Rust kurulumuyla birlikte yüklendiği anlamına gelir. Eğer Command not found gibi bir hatayla karşılaşıyorsanız Cargo'nun nasıl kurulacağına dair kullandığınız kurulum yöntemi belgelerini incelemelisiniz.

Cargo ile Proje Oluşturmak

Cargo aracını kullanarak yeni bir proje oluşturup, bu yeni projenin önceki projemiz olan "Merhaba dünya!" ile farklılıklarını incelemeye çalışalım. Başlangıçta açtığımız projeler dizini -ya da kodlarınızı nereye kaydediyorsanız- o dizine geçerek kullandığınız işletim sisteminden bağımsız olarak iş gören aşağıdaki komutları çalıştırın:

$ cargo new merhaba_cargo
$ cd merhaba_cargo

İlk komut, Cargo aracının "merhaba_cargo" adlı yeni bir dizin açmasını ve bu dizinde bir proje için gerekli olan dosyaları oluşturmasını sağlar.

Sonraki komut, Cargo aracının bizim için oluşturduğu dizine atlamamızı sağlar. Dizindeki dosyaları listelerseniz Cargo aracıyla oluşturulan Cargo.toml dosyası ve içinde main.rs dosyasını bulunduran bir src dizini göreceksiniz.

Ek olarak Cargo aracı .gitignore dosyasını içeren yeni bir git deposunun da başlatılmasını sağlar. Eğer cargo new komutunu halihazırda mevcut olan bir git deposu içinde çalıştırırsanız bu git dosyaları oluşturulmaz. Bu davranışı cargo new --vcs=git komutunu kullanarak baskılayabilirsiniz.

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ırabilirsiniz.

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"
edition = "2021"

[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. Bu dosyaya daha fazla bilgi ekledikçe, başka bölümler de ekleyeceğiz.

Sonraki üç satır, programınızın Cargo tarafından derlenebilmesi için gereken: İsim, programınızın sürüm bilgisi ve Rust sürümü gibi yapılandırma bilgilerinden oluşur. edition (Rust sürümü) anahtarı konusunu Ek E bölümünde işleyeceğ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() {
     // "Merhaba, Cargo" olarak değiştirebilirsiniz.
    println!("Hello, world!"); 
}

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 şeyi ait olduğu dizine yerleştirerek düzenli projeler oluşturmanızı sağlar.

"Merhaba, dünya!" örneğinde yaptığımız gibi Cargo kullanılmadan başlatılan bir projeyi, tıpkı Cargo ile oluşturulmuş gibi düzenleyebilirsiniz. Bunun için proje kaynak kodunu src dizinine taşıyarak, 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/rusdili/projeler/merhaba_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 1.82s

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

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

Her şey yolunda giderse terminalinizde "Merhaba, Cargo!" 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 burada değişiklik yapmanız gerekmez.

Sadece cargo build komutu ile derlediğimiz ve ./target/debug/merhaba_cargo komutu ile çalıştırdığımız bu projeyi, aynı anda derleyip çalıştırabilmek için cargo run komutunu kullanabiliriz.

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/merhaba_cargo`
Merhaba, Cargo!

Ancak bu defa Cargo'nun, merhaba_cargo programını derlediğini bildiren çıktının gösterilmediğine dikkat edin. Bu durum Cargo'nun kaynak kodun değiştilmediğini bilmesinden kaynaklanır. Kaynak kodunda değişiklik yaptığınmız programı Cargo ile yeniden derleyip çalıştıracak olursanız aşağıdakine benzer bir çıktı görürsünüz:

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

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

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

Bazı durumlarda çalıştırılabilir dosya oluşturmanız gerekmez. cargo check komutu yürütülebilir dosya oluşturma adımını atladığından çoğu zaman cargo build işleminden daha hızlı olacaktır. Geliştirme aşamasında kodunuzun çalışıp çalışmadığını düzenli olarak kontrol ediyorsanız cargo check komutu üretim sürecinizi hızlandıracaktır. Pek çok Rustacean geliştirme aşamasında, programlarının derlendiğinden emin olabilmek için düzenli olarak cargo check kontrolü yapar. Ancak çalıştırılabilir dosyayı kullanmaya hazır olduklarında cargo build komutunu çalıştırırlar.

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ışırsanız çalışın, kullanacağınız komutların değişmiyor olmasıdır. O nedenle bu noktadan itibaren Linux, macOS veya Windows işletim sistemlerinin her biri için ayrı talimatlar vermeyeceğiz.

Sürüm Amaçlı Derleme

Yayına hazır olan projenizi en iyileştirme olanakları ile derleyebilmek için cargo build --release komutunu kullanabilirsiniz. Bu komut çalıştırılabilir dosyanızı target/debug dizini yerine target/release dizinine çıkaracaktır. 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 de 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

Cargo basit projelerde rustc kullanımından olduğundan fazla yarar sağlamıyor olsa da geliştirme süreci karmaşıklaştıkça değerini kanıtlayacak bir araç setidir. O nedenle birden çok sandıktan oluşan karmaşık projelerde 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 gerçek araçların pek çoğunu kullanıyor. Var olan herhangi bir Rust projesinde çalışırken Git aracılığıyla kodu kontrol etmek, proje dizininine geçip kodu derlemek için aşağıdaki komutları kullanabilirsiniz.

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

Cargo hakkında daha fazla bilgi edinmek istiyorsanız Cargo belgelerini inceleyiniz.

Özet

Güzel başlayan Rust yolculuğunuzda aşağıdakileri öğrendiniz:

  • Rust'ı, rustup kullanarak en son kararlı sürümüyle 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 bunu rustc kullanarak doğrudan ç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

Birlikte uygulamalı bir proje üzerinde çalışarak Rust'ı kavramaya çalışalım! Bu bölümde size Rust'ın temel kavramlarından bazıları tanıtılacak ve bu kavramların gerçek bir programda nasıl kullanılacağı gösterilecektir. Bölüm boyunca let ve match anahtar kelimeleri, ilişkili metotlar ve işlevler, harici sandıklar gibi kavramlar üzerinde temel bilgilerinizi uygulayacak ve ilerleyen bölümlerde bu kavramlar ayrıntılarıyla incelenecektir.

Projemizde klasik bir programlama problemi olan sayı tahmin oyununu kodlayacağız. Program 1 ile 100 arasında rastgele bir sayı oluşturacak ve oyuncudan bu sayıyı tahmin etmesini isteyecektir. Oyuncu tahmin ettiği sayıyı girdiğinde bu değer, programın oluşturduğu sayı ile karşılaştırılacak, sayı yüksek veya düşükse bu bilgi oyuncu ile paylaşılarak yeniden tahmin girilmesi istenecek, doğru sayı bulunduğunda bir tebrik mesajı yazdırılarak programdan çıkılacaktır.

Yeni Bir Proje Oluşturmak

Yeni bir proje oluşturmak için 1. Bölümde oluşturduğumuz projeler dizinine giderek aşağıdaki komutları uygulayın:

$ cargo new tahmin_oyunu
$ cd tahmin_oyunu

İlk satırdaki cargo new komutu argüman olarak projeye verdiğimiz tahmin_oyunu adını alır. İkinci satırdaki cd tahmin_oyunu komutu bizi, Cargo tarafından oluşturulan bu yeni dizine yönlendirir.

Cargo tarafından otomatik oluşturulan Cargo.toml dosyasına göz atalım:

Dosya adı: Cargo.toml

[package]
name = "tahmin_oyunu"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

Birinci bölümden hatırlayacağınız gibi cargo new komutu size hazır bir "Hello, world!" programı sunar. src/main.rs dosyasını kontrol edelim:

Dosya adı: src/main.rs

fn main() {
    println!("Hello, world!");
}

Ve bu programı cargo run komutu kullanarak tek seferde derleyip çalıştıralım:

$ cargo run
   Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
    Finished dev [unoptimized + debuginfo] target(s) in 1.80s
     Running `target/debug/tahmin_oyunu`
Hello, world!

Sıklıkla kullanılan run komutu, bir projeyi çabucak derleyip çalıştırmamız ve bir sonraki derleme adımına hızlıca gitmemiz gerektiğinde oldukça faydalıdır.

Programımızı oluşturacağımız src/main.rs dosyasını yeniden açarak kodlamaya başlayalım!

Tahmin Verisinin İşlenmesi

Tahmin oyununun ilk bölümü, kullanıcılardan tahmin verisi olarak işleyebileceği bir değer girmesini isteyecek ve bu verinin beklenen biçimde olup olmadığını kontrol edecektir. Oyunun başlaması için oyuncunun bir tahmin değeri girmesine izin verilecektir. Örnek 2-1'de yer alan kodu src/main.rs dosyasına ekleyelim:

Dosya adı: src/main.rs

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Örnek 2-1: Bu kod kullanıcıdan tahmin verisini alarak ekrana yazdırır.

Bu kod fazla bilgi içerdiğinden her satırının ayrı ayrı nceleyelim. Kullanıcı girdisini alarak sonucu çıktıta yazdırabilmek için Rust standart kütüphanesi std'nin bir parçası olan io (input/output) kütüphanesini içe aktarmamız gerekir.

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Standart kütüphanede tanımlanmış ve Rust'ın varsayılan olarak her program kapsamına otomatik olarak dahil ettiği bir kaç öğe vardır.

Varsayılan haliyle Rust başlatılan her program kapsamına otomatik olarak birkaç türü dahil eder. prelude olarak adlandırılan bu setin içindekileri Standart kütüphane belgelerinde bulabilirsiniz.

Eğer kullanmak istediğiniz bir veri türü prelüd bölümünde bulunmuyorsa, bu türü use anahtar sözcüğü kullanarak açıkça kapsam içine almanız gerekir. Uygulamamızda kullandığımız std::io kütüphanesi, kullanıcı girdisini kabul etme yeteneği dahil bir dizi kullanışlı özellikle birlikte gelir.

Birinci bölümden hatırlayacağınız üzere main() işlevi programın giriş noktasını oluşturur.

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Function kelimesinin kısaltılmışı olan fn söz dizimi yeni bir işlev bildirirken, içi boş parantezler () işlevin herhangi bir giriş parametresi almadığını, açılış ayracı olarak da bilinen sağa bakan süslü parantez { ise işlev gövdesinin başlangıç noktasını gösterir.

Yine 1. Bölüm'den hatırlayacağınız üzere println!, bir dizgiyi ekrana yazdırmak amacıyla kullandığımız bir makrodur:

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Bu kod oyun hakkında bilgi veren ve kullanıcıdan girdi bekleyen bir komut istemi yazdırır.

Değerleri Değişkenlerde Saklamak

Şimdi aşağıda gösterildiği gibi kullanıcı girdisini depolayacağımız bir değişken oluşturacağız:

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Çok şeyin gerçekleştiği bu satırda program ilginçleşmeye başlıyor. Bu satırın değişken oluşturmak için kullanılan bir let ifadesiyle başladığına dikkat edin. İşte size başka bir örnek:

let elmalar = 5;

Bu satır elmalar adında yeni bir değişken oluşturarak onu 5 değerine bağlar. Rust'ta değişkenlerin varsayılan olarak değişmez oldukları kabul edilir. Bu kavramı 3. Bölümümüz olan "Değişkenler ve Değişkenlik" başlığı altında ayrıntılarıyla inceleyeceğiz. Bir değişkeni değiştirilebilir kılmak için değişken adının önüne mut anahtar kelimesini ekleriz:

let elmalar = 5;    // değişmez
let mut muzlar = 5; // değişebilir

Not: // söz dizimi satır sonuna kadar devam eden bir yorumu başlatır. Rust'ın derleme aşamasında görmezden geldiği yorum satırlarını 3. Bölümde tartışacağız.

Tahmin oyunumuzdaki let mut tahmin söz diziminin, içeriği değiştirilebilir olarak saklanan tahmin adında bir değişken tanımı olduğunu artık biliyorsunuz. Eşittir = işleciyle Rust'a, bu değişkene bir bir şeyler bağlamak istediğinizi bildirmiş olursunuz. Eşittir = işlecinin sağ tarafında, yeni bir dizgi örneği almak için kullandığımız String::new() işlevinden dönen ve tahmin değişkeninin bağlandığı değer bulunmaktadır. Dizgiler, UTF-8 baytlarıyla kodlanmış, boyutları değiştirilebilen ve standart kütüphane tarafından sağlanan String türündeki metin parçalarıdır.

String::new() satırındaki :: söz dizimi, new() işlevinin String türünün ilişkili işlevi olduğunu gösterir. İlişkili işlev; türe özgü, o türe ait bir uygulama olduğundan, bu durumda new işlevi yeni ve boş bir dizgi oluşturur. Genellikle new olarak adlandırılan ve ilişkili olduğu türün yeni bir değerini oluşturan bu işlevlerle Rust'ın birçok türünde karşılaşacaksınız.

Özetle let mut tahmin = String::new(); satırında bir String türünün yeni ve boş bir örneğiyle ilklendirilen değiştirilebilir bir değişken tanımlanmaktadır.

Kullanıcının Girdiği Veriyi Yakalamak

Hatırlayacağınız gibi programın ilk satırında use std::io söz dizimini kullanarak Rust standart kütüphanesinden giriş/çıkış işlevselliğini uygulamıştık. Şimdiyse io modülünde bulunan stdin işlevini çağıracağız:

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Eğer io kütüphanesini programın en başındaki use std::io satırınyla ithal etmemiş olsaydık, stdin işlev çağrısını, kod içinde std::io::stdin şeklinde yazarakta kullanabilirdik. stdin işlevi terminalinizdeki standart girdinin tanıtıcısını temsil eden bir std::io::Stdin tür örneği döndürür.

Sonraki .read_line(&mut tahmin) satırında, kullanıcıdan veri alacak olan standart girdi tanıtıcısındaki read_line metodunu çağırılarak kendisine, girdisinin saklanacağı dizgi olan &mut tahmin argümanı iletilir. read_line metodunun bütün işi, kullanıcı tarafından girilen her veriyi standart girişe almak ve bunları bir dizgi içine yerleştirmektir.Yöntemin, kullanıcı girdisi eklendikçe dizgi içeriğini değiştirilebilmesi için, kendisine iletilen argümanın değişebilir olması gerekmektedir.

& belirteci, bu argümanın referans türünden olduğunu bildirdiğinden, kodun bazı bölümleri tarafından bu değişkenlere, bellekte defalarca kopyalanmaları gerekmeksizin erişilmesi sağlanmış olur. Referanslar dilin güçlü ve karmaşık bir özelliğidir. Rust'ın önemli avantajlarından biri de referans kullanımının kolay ve güvenli olmasıdır. Bu programı bitirebilmeniz için daha fazla ayrıntı bilmenize gerek yok. Şimdilik tıpkı değişkenler gibi referansların da varsayılan olarak değişmez olduklarını ve onları değiştirilebilir kılabilmek için &tahmin yerine &mut tahmin yazmamız gerektiğini öğrenmemiz yeterlidir. (Referanslar konusu 4.Bölümde ayrıntılı olarak ele alınacaktır.)

Result Türünü Kullanarak Olası Hataları İşlemek

İncelememize io::stdin ile başlayan ifadenin üçüncü satırıyla devam edelim. Her ne kadar ayrı bir satırmış gibi görünmesine rağmen, bu satır da tıpkı bir önceki satır gibi, aynı mantıksal kod satırının parçası olup koda expect metodunu eklemektedir:

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Oysa bu kodu bu şekilde de yazabilirdik:

io::stdin().read_line(&mut tahmin).expect("Veri okuma hatası!");

Fakat böyle uzun satırları okumak zor olduğundan en iyisi onu parçalara ayırmaktır. Bir metodu .yöntem_adı() söz dizimiyle çağırdığınızda, uzun ifadeleri mantıksal parçalara bölebilmeniz için genellikle yeni satırlar ve boşluklar eklemeniz mantıklı olur. Şimdi bu satırın ne anlama geldiğini inceleyelim.

Daha önce bahsettiğimiz gibi read_line işlevi, kullanıcı tarafından girilen verileri kendisine ilettiğimiz dizgiye depolarken, bu işin gerçekleştirilmesi sırasında oluşabilecek hataların izlenebilmesi için io::Result türünde bir değer döndürür. Rust standart kitaplığı Result olarak adlandırılan, generic türler ve io::Result gibi alt modüllerle kullanılmak üzere bir tür bulundurur. Varyant olarak bilinen ve sabit olasılık kümelerinden oluşan enums türleri genellikle eşleme işlemlerinde kullanılır. Enum kullanan eşleme işlemlerinde değerlendirilen koşul enum değerinin hangi varyantına uyuyorsa kodun o bölümü çalıştırılır.

Hata işleme bilgilerinin kodlanmasını amaçlayan Result türünü 6. Bölümde ayrıntılarıyla ele alacağız.

Result türünün Ok ve Err adında iki varyantı bulunur. Ok varyantı, işlem sonucunun başarılı olması durumunda döndürülen değere ev sahipliği yaparken, işlemin başarısız olması anlamına gelen Err varyantında ise bu başarısızlığın nasıl ve neden olduğunu açıklayan bilgiler depolanır.

Herhangi bir türün değerleri için olduğu gibi Result türünün değerleri için de tanımlanmış ilişkili metodlar bulunur. Bu bağlamdaio::Result örneğinin de expect adında bir metodu bulunmaktadır. Bu metot çağrıldığında, io..Result örneği Err değeri taşıyorsa expect programın çökmesine neden olacak ve kendisine argüman olarak ilettiğiniz mesajı görüntüleyecektir. read_line metodunun Err değerini döndürmesi genellikle işletim sisteminden kaynaklanan bir hatadır. Bununla birlikte io::Result örneği Ok değerini taşıyorsa, expect metodu Ok içinde saklanan dönüş değerini alarak kullanmanız için size döndürecektir. Bu durumda döndürülen Ok değeri kullanıcı tarafından standart girdiye iletilen bayt sayısından ibaret olacaktır.

Bu aşamada expect metodunu çağırmasanız bile programınız derlenir fakat aşağıdaki gibi bir uyarı alırsınız:

$ cargo run 
   Compiling no-listing-02-without-expect v0.1.0 (/home/rusdili/projeler/tahmin/oyunu)
warning: unused `Result` that must be used
  --> src/main.rs:10:5
   |
10 |     io::stdin().read_line(&mut guess);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled

warning: `no-listing-02-without-expect` (bin "no-listing-02-without-expect") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.86s

Rust read_line tarafından döndürülen Result değerini kullanmadığınızı ve programın olası bir hatayı işlemediğini bildirmektedir.

Her ne kadar uyarıları bastırmanın doğru yolu bir hata işleyici yazmak olsada, şu aşamada sorun oluştuğunda programın çökmesini istediğimizden expect metodunu kullanmak zorundayız. Hata işlemek konusunu kitabın 9. Bölümünde. ayrıntılarıyla inceleyeceğiz.

Println! Yer Tutucuları ile Değerleri Yazdırmak

Kodun sonlandığı noktayı gösteren kapanış ayracı (sola bakan süslü parantez) haricinde değerlendirilmesi gereken bir satırımız daha var:

use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
    	.read_line(&mut tahmin)
    	.expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Bu satır kullanıcı girdisini kaydettiğimiz dizgiyi ekrana yazdırabilmek için vardır. Yer tutucuları temsil eden süslü parantezleri {} ise bir değerin yerini tutan yengeç kıskaçlarına benzetebilirsiniz. Çok sayıda değerin gösterilmesi amacıyla da kullanabileceğiniz bu parantezlerin ilk çifti, biçimlendirilmiş dizgiden sonraki ilk değeri içerirken, sonraki parantez ikinci değeri, bir sonraki üçüncü değeri gösterecektir. İki farklı değişkenin değerlerini ekrana yazdıran örnek println! çağrısı aşağıdakine benzeyecektir:


#![allow(unused)]
fn main() {
let x = 5;
let y = 10;

println!("x değeri = {}, y değeri = {}", x, y);
}

Bu örnek ekrana x değeri = 5, y değeri = 10 yazdıracaktır.

İlk Bölümü Test Etmek

Programın ilk bölümünü test etmek için cargo run komutunu çalıştırın:

$ cargo run                                                   ✔ 
   Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
    Finished dev [unoptimized + debuginfo] target(s) in 1.34s
     Running `/home/rusdili/projeler/tahmin_oyunu/target/debug/tahmin_oyunu`
Tuttuğum sayıyı tahmin edin!
Tahmininizi girin.
6
Tahmininiz: 6

Klavyeden girdi alıp onu ekrana yazdırabildiğimize göre oyunun ilk bölümü tamamlanmış demektir.

Gizli Sayıyı Oluşturmak

Şimdi kullanıcının tahmin edeceği gizli sayıyı oluşturmamız gerekiyor. Oyunu eğlenceli ve tekrar oynanabilir kılabilmek amacıyla gizli sayıyı her defasında değiştirmemiz gerekir. Oyunu kolaylaştırmak için de, tahmin edilecek sayıyı 1 ile 100 arasında ve tesadüfi biçimde seçmeliyiz. Rust'ın standart kitaplığı rastgele sayı oluşturabilecek işlevselliği henüz barındırmıyor. Ancak Rust ekibi bu işlevsellik için rand adlı harici bir sandık sunar.

İlave İşlevsellik İçin Sandık Kullanmak

Sandık, Rust kaynak kodu dosyalarının bir araya getirilmiş halidir. Geliştirmekte olduğumuz bu proje bile aslında bir çalıştırılabilir ikili sandık (binary crate) sandıktır. Bize harici olarak sunulan rand sandığı başka programlarda kullanılması amaçlanan kodları içeren bir kitaplık sandığıdır.

Harici sandıkların koordinasyonu, Cargo özelliklerinin ışıldadığı yerdir. Rand sandığı kullanan bir kod yazabilmek için önceklikle Cargo.toml dosyasının bu bağımlılığı içerecek şekilde güncellenmesi gerekir. Bunu gerçekleştirebilmek için aşağıdaki satırları, Cargo.toml dosyasında yer alan [dependencies] başlığının altına doğru şekilde ekleyin. Kodun sağlıklı çalışabilmesi için Rand sandığını buradaki gibi aynı sürüm numarasıyla bildirdiğinizden emin olun:

Dosya adı: Cargo.toml

[dependencies]
rand = "0.8.3"

Cargo.toml dosyasındaki bölüm başlıklarının altına gelen her şey, başka bir bölüm başlayana dek o bölümün parçasıdır. Bağımlılıklar yani [dependencies] bölümünde Cargo'ya, projenizin çalışabilmesi için ihtiyaç duyduğu harici sandıkları ve bu sandıkların hangi sürümlerini kullanacağınızı bildirirsiniz. Bu durumda biz de projemizde kullanacağımız rand sandığı sürümünü 0.8.3 olarak beldireceğiz. Cargo, sürüm numaralarını bildirmekte standart olarak kullanılan anlamsal sürümleme sistemini -SemVer olarak da adlandırılır- yorumlamayı bildiğinden, 0.8.3'ün aslında ^0.8.3'ün kısaltması olduğunu anlar. Bağımlılık olarak bildirdiğimiz rand sandığının sürüm numarası 0.8.3, projemizin en az 0.8.3 olan ancak 0.9.0'ın altında kalan herhangi bir sürümle çalışabileceği anlamına gelmektedir. Bu durumda Cargo, 0.8.3'den 0.9.0'a kadar olan olası sandık sürümlerinin, 0.8.3 sürümüyle uyumlu genel API'ye sahip olduğunu varsayarak, projemizin derlenebilmesi için gereken en son sürümü ediner ve projemizin çalışmasını sağlar. Bununla birlikte 0.9.0 veya daha sonraki herhangi bir sürümün aşağıdaki örneklerin kullandığı API ile aynı API'ye sahip olacağı garanti edilmez.

Şimdi herhangi bir kod değişikliği yapmadan Tıpkı Örnek 2-2'de gösterildiği haliyle projeyi oluşturalım.

$ cargo build
  Updating crates.io index
  Downloaded rand v0.8.3
  Downloaded libc v0.2.86
  Downloaded getrandom v0.2.2
  Downloaded cfg-if v1.0.0
  Downloaded ppv-lite86 v0.2.10
  Downloaded rand_chacha v0.3.0
  Downloaded rand_core v0.6.2
  Compiling rand_core v0.6.2
  Compiling libc v0.2.86
  Compiling getrandom v0.2.2
  Compiling cfg-if v1.0.0
  Compiling ppv-lite86 v0.2.10
  Compiling rand_chacha v0.3.0
  Compiling rand v0.8.3
  Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
   Finished dev [unoptimized + debuginfo] target(s) in 0.28s

Örnek 2-2: Bağımlılık olarak eklenen rand sandığı sonrasında cargo build komutuyla elde edilen çıktı.

Derleme esnasında oluşan çıktı işletim sisteminize bağlı olarak değişebileceğinden derlenen paket adları ve sürüm numaraları ekranınızda farklı sırayla yansıtılabilir. Bununla birlikte yüklenen sürümler anlamsal sürümleme sayesinde kodumuzla uyumlu olacaktır.

Harici bir bağımlılık eklediğimizde Cargo, Crates.io'daki verilerin bir kopyası olan kayıt defterinden, ihtiyaç duyduğumuz tüm bağımlılıkların en son sürümlerini çekecektir. Crates.io, Rust ekosistemindeki geliştiricilerin açık kaynak projelerini başkaları ile paylaşmak amacıyla sandıklar şeklinde yayınladıkları çevrimiçi bir kaynaktır.

Kayıt defteri güncellendikten sonra Cargo, [dependencies] bölümünü kontrol ederek henüz sahip olmadığımız sandıkları indirir. Bağımlılık olarak yalnızca rand kütüphanesi eklense bile, Cargo bu kütüphanenin çalışabilmesi için gerekli diğer sandıkları da indirecektir. Gerekli sandıklar indirildikten sonra Rust önce bu sandıkları derleyecek, arkasından projemizi mevcut bağımlılıklar ile yeniden oluşturacaktır.

Herhangi bir değişiklik yapmadan cargo build komutunu yeniden çalıştırırsanız, uçbiriminizde Finished satırınndan başka çıktı alamazsınız. Bu eylemsizlik Cargo'nun; bağımlılıkların indirilip derlendiğini, kodda değişiklik yapılmadığını ve Cargo.toml dosyasının aynı kaldığını bilmesinden kaynaklanır. Bu durumda yapacak bir şey olmadığını fark eden Cargo programı derlemeden süreci sonlandırır.

Fakat src/main.rs dosyasını açıp üzerinde basit bir değişiklik yaparak kaydedip derlerseniz, yalnızca iki satırdan oluşan aşağıdaki çıktıyla karşılaşırsınız:

$ cargo build
   Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s

Bu satırlar derlemenin sadece src/main.rs dosyasındaki küçük değişiklikler gözetilerek gerçekleştirdildiğini gösterir. Bağımlılıkların değişmediğini ve projenin, önceden indirilip derlenen bağımlılıklarla kullanılmasının mümkün olduğunu anlayan Cargo, kodu sadece değişen kısmıyla yeniden oluşturur.

Cargo.lock Dosyası ile Yinelenebilir Derlemeler

Cargo, siz veya başkaları tarafından kodunuzun her derlenişinde aynı yapıyı yeniden oluşturan bir mekanizmaya sahiptir. Bu Cargo'nun siz aksini söyleyene kadar sadece bildirdiğiniz bağımlılık ve sürümlerini kullanması anlamına gelir. Örneğin rand sandığının yeni sürümü 0.8.4'ün önemli bir hata düzeltmesiyle yakın bir zamanda yeniden yayınlanacağını varsayalım. Bu durumda ne olacağının yanıtı, cargo build komutunu ilk çalıştırdığınızda tahmin_oyunu dizininde oluşturulan Cargo.lock dosyasında bulunmaktadır.

Bir projeyi ilk kez derlediğinizde kriterlere uyan tüm bağımlılık sürümleri Cargo tarafından belirlenerek Cargo.lock dosyasına yazılır. Daha sonra projenin yeniden derlemmesi gerektiğinde Cargo, Cargo.lock dosyasının halihazırda var olduğunu görecek ve tüm sürüm oluşturma işlemlerini yapmak yerine, orada belirtilmiş sürümleri kullanacaktır. Bu sizin otomatik olarak tekrarlanabilir derlemelere sahip olmanızı sağlar. Başka bir ifadeyle, Cargo.lock dosyası sayesinde projeniz siz yeniden ve açıkça yükseltme yapmadığınız sürece 0.8.3 sürümünde kalmaya devam eder.

Bir Sandığı Yeni Bir Sürüme Güncellemek

Bir sandığı güncellemek istediğinizde Cargo size, Cargo.lock dosyasını yok sayacak ve Cargo.toml dosyanızdaki kriterlere uygun son sürümleri bulmanızı sağlayacak update adında bir komut daha sağlar. Süreç başarıyla tamamlanırsa güncellenen bu sürümler Cargo.lock dosyasına yazılır. Ancak güncelleme esnasında varsayılan olarak sadece 0.8.3'ten büyük 0.9.0'dan küçük olan sürümler aranacaktır. Eğer rand sandığı için 0.8.4 ve 0.9.0 olmak üzere iki yeni sürüm yayınlanmışsa update komutunu çalıştırdığınızda aşağıdaki gibi bir çıktı görünecektir:

$ cargo update
    Updating crates.io index
    Updating rand v0.8.3 -> v0.8.4

Bu noktada Cargo.lock dosyanızda kullanmakta olduğunuz rand sandığı sürümünün, 0.9.0 sürümünün yok sayılarak 0.8.4'e yükseltildiğini belirten değişikliğin yapıldığını fark edeceksiniz. Eğer rand sandığının 0.9.0 veya 0.9.x sürümlerinden birini kullanmak isterseniz, Cargo.toml dosyanızı aşağıdaki şekilde güncellemeniz gerekir:

[dependencies]
rand = "0.9.0"

cargo build komutunu yeniden çalıştırdığınızda, Cargo mevcut sandıkların kayıtlarını güncelleyerek rand kütüphanesi gereksinimlerini bildirdiğiniz yeni sürüme göre yeniden değerlendirecektir.

Cargo ve Ekosistemi hakkında söylenecek çok şey olmasına rağmen bunları, 14. Bölümde enine boyuna tartışacağız. Şimdilik Cargo'nun, kitaplıkların yeniden kullanımını kolaylaştırarak geliştiricilerin, bir dizi paketten oluşan küçük projeler yazabilmelerini sağladığını bilmemiz yeterlidir.

Rastgele Sayının Üretilmesi

Artık rastgele sayıyı üretebilmek için rand sandığını kullanabiliriz. Yapacağımız ilk şey src/main.rs dosyamızı örnek 2-3'te olduğu gibi güncellemektir.

Dosya adı: src/main.rs

use std::io;
use rand::Rng;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    let gizli_sayı = rand::thread_rng().gen_range(1..101);

    println!("Gizli sayı: {}", gizli_sayı);

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
        .read_line(&mut tahmin)
        .expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);
}

Örnek 2-3: Rastgele sayı üretmek için eklenen kodlar.

Önce projemizin kapsam alanına use rand::Rng şeklinde bir use satırı ekliyoruz. Rand kitaplığının Rng özelliği, rastgele sayı üreteçlerinin uyguladığı metotları tanımladığından, bu yöntemin kullanabilmesi için kütüphanenin kapsama dahil edilmesi gerekir. Özellikler (trait) konusuna 10. Bölümde değineceğiz.

Ardından ilk ekran çıktısını üreten satırdan sonra iki satır daha ekleyeceğiz. Bu satırlardan ilki olan rand::thread_rng() işlevinde, işletim sistemi tarafından başlatılan ve geçerli olan iş parçacığına özgü kullanılan rastgele sayı üreteci başlatılacak ve üretilecek olan sayı ı adlı değişkende saklanacaktır. Bu sayının üretiminde ise rand::Rng olarak kapsama alanına dahil ettiğimiz Rng özelliğinde tanımlanmış gen_range() metodundan yararlanılacaktır. Kendisine verilen bir aralığa göre rasgele sayı üreten gen_range() metodunda kullanılan aralık ifadesi başlangıç..bitiş şeklinde olup, başlangıç olarak verilen alt sınır değeri kapsanmakta, bitiş olarak verilen üst sınır değeri ise hariç tutulmaktadır. Bu nedenle 1 ile 100 arasındaki sayılar arasından birini rastgele olarak talep edebilmemiz için metoda ileteceğimiz aralık değerlerini, aralığa dahil edilecek olan 1 ile aralığa dahil edilmeyecek olan üst sayı sınırını bildiren 101 olarak bildirmemiz gerekir. Eğer bu ifade biçimi size karışık geliyorsa, aynı işi yapan ve hem başlangıç hem de bitiş değerlerini aralığa dahil olarak gösterebileceğiniz 1..=100 şeklindeki gösterimi gen_range() metoduna aralık olarak iletebilirsiniz.

Bir sandığın hangi özellik, metot ve işlevlerinin kullanılabileceğini her zaman bilemeyebilirsiniz. Sandıkların nasıl kullanılması gerektiğine dair talimatlar o sandığa ait belgelerde yer almaktadır. Cargo'nun bir başka güzel özelliği de, tüm bağımlılıklarınız tarafından sağlanan dökümantasyonu yerel olarak oluşturup, tarayıcınızda uyumlu olarak çalıştıracak olan cargo doc --open komutunu sağlamasıdır. örneğin rand sandığındaki bulunan diğer işlevler hakkında bilgilenmek istiyorsanız, cargo doc --open komutunu çalıştırarak, sol kenar çubuğunda yer alan rand seçeneğine tıklamanız yeterlidir.

Eklediğimiz ikinci satır ise gizli_sayı değişkenini yazdırmak için kullanılacaktır. Kodumuzun gelişme aşamasında test amaçlı kullanacağımız bu satır, programımızın nihai sürümünde yer almayacaktır. Başlatılır başlatılmaz gizli kalması gereken sayıyı açık eden program oyun değildir!

Programı birkaç defa çalıştırarak deneyin:

$ cargo run
   Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
     Running `/home/rusdili/projeler/tahmin_oyunu/target/debug/tahmin_oyunu`
Tuttuğum sayıyı tahmin edin!
Gizli sayı: 73
Tahmininizi girin.
11
Tahmininiz:: 11

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `/home/rusdili/projeler/tahmin_oyunu/target/debug/tahmin_oyunu`
Tuttuğum sayıyı tahmin edin!
Gizli sayı: 69
Tahmininizi girin.
88
Tahmininiz:: 88

Program her çalıştırıldığında 1 ile 100 arasında tesadüfi bir sayı göstermelidir. Güzel iş!

Tahmin Sayısının Gizli Sayı ile Karşılaştırılması

Elimizde kullanıcıdan alınan bir tahmin sayısı ve tasadüfi olarak üretilen bir gizli_sayı olduğuna göre bunları karşılaştırabiliriz. Kodun bu bölümü Örnek 2-4'te gösterilmekle beraber, henüz açıklayacağımız nedenlerden ötürü derlenmez.

Dosya adı: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    // --snip--
    println!("Tuttuğum sayıyı tahmin edin!");

    let gizli_sayı = rand::thread_rng().gen_range(1..101);

    println!("Gizli sayı: {}", gizli_sayı);

    println!("Tahmininizi girin.");

    let mut tahmin = String::new();

    io::stdin()
        .read_line(&mut tahmin)
        .expect("Veri okuma hatası!");

    println!("Tahmininiz: {}", tahmin);

    match tahmin.cmp(&gizli_sayı) {
        Ordering::Less => println!("Sayınız küçük!"),
        Ordering::Greater => println!("Sayınız büyük!"),
        Ordering::Equal => println!("Kazandınız!"),
    }
}

Örnek 2-4: İki sayıyı karşılaştırarak olası dönüş değerlerini işlemek.

Buradaki ilk yenilik standart kitaplıktaki, std::cmp::Ordering; türünün yeni bir use deyimi kullanılarak kod kapsamına getirilmiş olmasıdır. Result türü gibi bir enum olan Ordering türünün less, Greater, Equal şeklinde üç karşılaştırma varyantı vardır ve bunlar, iki değeri karşılaştırırken ortaya çıkan üç olası sonucu temsil etmekte kullanılırlar.

Koda eklenen ikinci yenilik ise, Ordering türünü kullanmak amacıyla kodun en alt kısmına yerleştirdiğimiz beş yeni satır içeren bir eşleme ifadesidir. İfadenin kullandığı cmp metoduysa bir karşılaştırma işlevidir ve burada iki değerin karşılaştırılması amacıyla kullanılır. Karşılaştırılması istenen değerin referansını alarak çalışan bu metot, tahmin değişkeni içindeki değeri gizli_sayı değişkenindeki değer ile karşılaştıracak ve use anahtar kelimesiyle kod kapsamına aldığımız Ordering türünün varyantlarından uygun olan birini döndürecektir. Elde edilen dönüş değeriyle ne yapılacağına ise tahmin ve gizli_sayı değerlerini karşılaştıran cmp çağrısından döndürülecek olası sonuçlarla eşleştirilen ifadelerle karar verilecektir.

Dilimize eşleme olarak çevirebileceğimiz match olası durumları ifade eden dallardan meydana gelir. Bu dallar, bir örüntü (kalıp, şablon) ve eşleme ifadesinin başlangıcında belirtilen değerin bu örüntüyle eşleşmesi halinde yürütülecek olan koddan ibarettir. Eşleştirilecek değeri alan Rust bunu sırasıyla her dalın örüntüsüyle karşılaştıracak ve eşleşen daldaki kodu işletecektir. Rust'ın match yapısı ve örüntüleri, kodunuzda karşılaşabileceğiniz çeşitli durumları ifade etmenize yarayan ve olası her durumun ele alındığından emin olmanızı sağlayan güçlü özelliklerdir. Bu özellikler sırasıyla 6. ve 18. bölümlerde ayrıntılı biçimde ele alınacaktır.

Burada kullanılan eşleme ifadesinin nasıl çalışacağını anlayabilmek için kullanıcının tahmin ettiği sayının 50, rasgele üretilen sayının da 38 olduğunu varsayalım. Kod 50 ile 38 sayılarını karşılaştırdığında, 50 sayısı 38'den büyük olduğundan cmp metodu Ordering::Greater döndürecek ve match ifadesi Ordering::Greater değerini alarak her dalın örüntüsünü teker teker kontrol etmeye başlayacaktır. İlk dalın Ordering::Less örüntüsü kontrol edildiğinde, bu değerin Ordering::Greater ile eşleşmediği görülecek ve bu daldaki kodlar yok sayılarak hemen bir sonraki dala geçilecektir. Geçilen bu dal incelendiğinde, daldaki Ordering::Greater örüntüsünün match ifademizin almış olduğu Ordering::Greater değeriyle aynı olduğu görülecek ve bu koldaki kodlar çalıştırılarak ekrana Sayınız büyük! mesajı yazdırılacaktır. Artık bir eşleme bulunmuş olduğundan match ifadesi kalan son dala bakmaya gerek duymayacak ve çalışmasını sonlandıracaktır.

Ancak Örnek 2-4'ü çalıştırdığımızda henüz derlenmediğini görürüz:

$ cargo run
   Compiling libc v0.2.112
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.16
   Compiling getrandom v0.2.4
   Compiling rand_core v0.6.3
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.4
   Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
error[E0308]: mismatched types
   --> src/main.rs:343:22
    |
343 |     match tahmin.cmp(&gizli_sayı) {
    |                      ^^^^^^^^^^^ expected struct `String`, found integer
    |
    = note: expected reference `&String`
               found reference `&{integer}`

error[E0283]: type annotations needed for `{integer}`
   --> src/main.rs:328:41
    |
328 |     let gizli_sayı = rand::thread_rng().gen_range(1..101);
    |         ----------                      ^^^^^^^^^ cannot infer type for type `{integer}`
    |         |
    |         consider giving `gizli_sayı` a type
    |
    = note: multiple `impl`s satisfying `{integer}: SampleUniform` found in the `rand` crate:
            - impl SampleUniform for i128;
            - impl SampleUniform for i16;
            - impl SampleUniform for i32;
            - impl SampleUniform for i64;
            and 8 more
note: required by a bound in `gen_range`
   --> /home/rusdili/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.8.4/src/rng.rs:131:12
    |
131 |         T: SampleUniform,
    |            ^^^^^^^^^^^^^ required by this bound in `gen_range`
help: consider specifying the type arguments in the function call
    |
328 |     let gizli_sayı = rand::thread_rng().gen_range::<T, R>(1..101);
    |                                                  ++++++++

Some errors have detailed explanations: E0283, E0308.
For more information about an error, try `rustc --explain E0283`.
error: could not compile `tahmin_oyunu` due to 2 previous errors

Çıktıda sorunun tür uyumsuzluğundan kaynaklandığı belirtiliyor. Rust güçlü ve statik tür sistemiyle birlikte türün bağlamdan çıkarsanması özelliğine de sahip bir programlama dili olduğundan, tahmin değişkenini let mut tahmin = String::new() olarak bildirdiğimizde, değişkenin String türünde olacağını varsayar. Fakat programın rastgele ürettiği gizli_sayı ise sayı türüdür. Rust'ta 1 ile 100 arasındaki sayıları gösterebilecek belli başlı sayısal türler vardır. Bunlar, işaretli 32 bitlik sayılar için i32, işaretsiz 32 bitlik sayılar için u32, işaretli 64 bitlik sayılar için kullanılan i64 gibi türleridir. Rust tamsayılar için varsayılan olarak i32 türünü benimsediğinden, tür bilgisi kodun herhangi bir yerinde açıkça belirtilmedikçe i32 olarak varsayılacak, gizli_sayı değişkeni i32 olarak atanacaktır. Bu durumda bir String türüyle i32 türü karşılaştırılamayacağından Rust, tam olarak karşılaştığımız hatayı üretecektir.

Bu sorunu çözebilmemiz için, kullanıcı girdisi olarak okunan String türünü gerçek bir sayı türüne dönüştürüp, sayısal değerli gizli_sayı değişkeniyle karşılaştırmamız gerekir. Bunu main() işlevine ekleyeğimiz tek satır kod ile gerçekleştirebiliriz:

Dosya adı: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    let gizli_sayı = rand::thread_rng().gen_range(1..101);

    println!("Gizli sayı: {}", gizli_sayı);

    println!("Tahmininizi girin.");

    // --snip--

    let mut tahmin = String::new();

    io::stdin()
        .read_line(&mut tahmin)
        .expect("Veri okuma hatası!");

    let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı türü girin!");

    println!("Tahmininiz: {}", tahmin);

    match tahmin.cmp(&gizli_sayı) {
        Ordering::Less => println!("Sayınız küçük!"),
        Ordering::Greater => println!("Sayınız büyük!"),
        Ordering::Equal => println!("Kazandınız"),
    }
}

Eklenen yeni satır:


#![allow(unused)]
fn main() {
let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı türü girin!");
}

Bu satır tahmin adında yeni bir değişken oluşturur. Hatırlarsanız programımızda kullanılan bir tahmin değişkeni zaten vardı. O halde bu satırda yeniden oluşturulan tahmin değişkenin anlamı nedir? Rust bir değişkeni, aynı adlı başka bir değişkenle değiştirmemize izin verir. Gölgeleme olarak adlandırılan bu özellik, bir değeri olduğu türden başka bir türe çevirmek istediğiniz durumlarda oldukça kullanışlıdır. Bu özellik örneğin tahmin ve bir_başka_tahmin gibi iki farklı değişken oluşturmak yerine tahmin değişken adını tekrar kullanmamıza izin verir. Sıklıkla bir türün başka bir türe dönüştürülmesinde kullanılan Gölgeleme olanağını 3. Bölümde tartışacağız.

Yenitahmin değişkenini tahmin.trim().parse() ifadesine bağladığımızda, ifade içindeki tahmin, String türündeki kullanıcı girdisini içeren orjinal tahmin değişkenini gösterir. Bir String örneğine uygulanan trim metodu ise kendisine iletilen dizginin baş ve sonunda bulunan beyaz boşlukları temizler. Her ne kadar u32 türü yalnızca sayısal karakterler içeriyor olsa da, kullanıcının read_line işlemini yerine getirmek için enter tuşuna basmasıyla dizgiye yeni bir satır eklenecektir. Örneğin, kullanıcı tahmini ettiği 5 rakamını yazıp enter tuşuna bastığında, tahmin içindeki veri 5\n olarak görünecektir. Bu, kullanıcının girdiği rakama İngilizce karşılığı "newline" olan ve yeni bir satırı temsil eden \n karakterinin eklenmesi anlamına gelir. trim metodunun kullanılması, \n karakterinin temizlenerek girdinin sadece 5 olarak kalmasını sağlar.

Dizgilerle kullanılan parse metodu ise, dizgiyi sayı türüne ayrıştırır. Bu metot çeşitli sayı türlerini ayrıştırabildiğinden, istenilen sayı türünün Rust'a tam olarak let tahmin: u32 şeklinde açıkça bildirilmesi gerekir. tahmin değişkeninden sonra gelen (:) iki nokta ise, bildirilen değişkene tür açıklaması ekleneceğini gösterir. Rust'ta birkaç yerleşik sayısal tür bulunur ve burada kullandığımız u32 türü, işaretsiz 32 bitlik bir tamsayıyı olduğundan, küçük bir pozitif sayı için uygun bir seçimdir (Diğer sayı türlerini 3. Bölümde inceleyeceğiz.). tahmin değişkenine u32 olarak eklenen tür açıklaması ve tahmin değişkeninin gizli_sayı ile karşılaştırılması sayesinde Rust, bu bağlamdan gizli_sayı değişken türünün u32 olacağını çıkarır. Artık karşılaştırma işlemi, aynı türden iki değer arasında gerçekleştirilecektir!

Dizgi içeriğinde A👍% şeklinde bir değerin bulunması halinde, bu değeri bir sayıya sorunsuzca dönüştürmenin herhangi bir yolu olmadığından, parse çağrısı kolaylıkla bir hata üretebilir. Bu nedenle parse metodu, başarısız olma ihtimaline karşı daha önce Result Türü ile Olası Hataları İşlemek başlığında incelediğimiz gibi ve read_line metoduna benzer şekilde bir Result türü döndürür. Döndürülen Result türünü ise expect metodunu kullanarak değerlendireceğiz. Eğer parse metoduyla dizgiden bir sayı elde edilemez ve Result türü Err varyantını döndürürse expect çağrısı programı çökertecek ve kendisine parametre olarak ilettiğimiz Lütfen bir sayı türü girin! mesajını gösterecektir. Fakat parse metodu başarılı olur ve bir sayı üretebilirse, Result türü Ok varyantını döndüreceğinden expect çağrısından da Ok varyantı içinde depolanan bu değer döndürülmüş olacaktır.

Şimdi programımız yeniden çalıştıralım!

$ ccargo run                                          ✔  5s  
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/tahmin_oyunu`
Tuttuğum sayıyı tahmin edin!
Gizli sayı: 14
Tahmininizi girin.
76
Tahmininiz: 76
Sayınız büyük!

Kullanıcı girdisiyle alınan 76 sayısının önünde boşluklar olmasına rağmen kodun, tahmin değerini 76 olarak alınabiliyor olması güzel! Lütfen programınızı "Sayınız küçük!", "Sayınız büyük!" ve "Bildiniz!" seçeneklerini üretecek şekilde birkaç defa çalıştırarak gözlemleyin.

Oyunun büyük bölümü doğru çalışıyor olsa da kullanıcıların yalnızca bir tahmin hakkı olması bütün eğlenceyi bozuyor. Koda bir döngü ekleyerek bu durumu değiştirebiliriz!

Döngü Kullanarak Farklı Tahminler Almak

Bir anahtar kelime olan loop sonsuz döngü oluşturur. Kullanıcıların doğru sayıya ulaşmalarını kolaylaştırmak amacıyla programımıza loop döngüsü ekleyecek ve onlara daha fazla şans vereceğiz.

Dosya adı: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    let gizli_sayı = rand::thread_rng().gen_range(1..101);

    // --snip--

    println!("Gizli sayı: {}", gizli_sayı);

    loop {
        println!("Tahmininizi girin.");

        // --snip--


        let mut tahmin = String::new();

        io::stdin()
            .read_line(&mut tahmin)
            .expect("Veri okuma hatası!");

        let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı türü girin!");

        println!("Tahmininiz: {}", tahmin);

        match tahmin.cmp(&gizli_sayı) {
            Ordering::Less => println!("Sayınız küçük!"),
            Ordering::Greater => println!("Sayınız büyük!"),
            Ordering::Equal => println!("Kazandınız!"),
        }
    }
}

Göreceğiniz gibi 'tahmin giriş talebi'nden itibaren olan her şeyi döngü kapsamına taşıyarak, her satır için dört boşluk değerinde girinti oluşturduk. Programı çalıştırdığınızda kodun tam olarak istediğimiz şeyi yapmakla beraber, sonsuza kadar tahmin yapılmasını bekleyen yeni bir sorunun oluştuğunu ve kullanıcıların bu döngüden çıkmasının engellediğini fark edeceksiniz!

Kullanıcılar ctrl+d klavye kısa yolunu kullanarak programı her zaman sonlandırabilirler. Ancak bu doyumsuz canavardan kaçmanın başka bir yolu daha var. Hatırlarsanız Tahmin Sayısının Gizli Sayı ile Karşılaştırılması başlığındaki parse konusundan tartıştığımız gibi, tahmin verisine sayısal olmayan bir değer verilmesiyle programın çökerek sonlanıyordu. O haldei kullanıcıların döngüyü kırarak programdan çıkmalarını sağlamak için bundan yararlanabiliriz.

$ cargo run 
   Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
    Finished dev [unoptimized + debuginfo] target(s) in 0.37s
     Running `target/debug/tahmin_oyunu`
Tuttuğum sayıyı tahmin edin!
Gizli sayı: 26
Tahmininizi girin.
45
Tahmininiz: 45
Sayınız büyük!
Tahmininizi girin.
11
Tahmininiz: 11
Sayınız küçük!
Tahmininizi girin.
30
Tahmininiz: 30
Sayınız büyük!
Tahmininizi girin.
çıkış
thread 'main' panicked at 'Lütfen bir sayı türü girin!: ParseIntError { kind: InvalidDigit }', src/main.rs:178:49
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Tahmin değişkenine çıkış gibi sayısal olmayan herhangi bir ifadenin girilmesi programdan çıkılmasına yetiyor gibi görünse de bu mekanizma, "Tahmin sayısının doğru girilmesi halinde programın otomatik olarak sonlanması" talebimizi henüz karşılamıyor.

Doğru Tahmin Sonrası Oyundan Çıkmak

Kullanıcının doğru tahmin yaparak oyunu kazanması durumunda, programdan çıkılmasını sağlayan break anahtar kelimesini kodlarımıza ekleyelim:

Dosya adı: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    let gizli_sayı = rand::thread_rng().gen_range(1..101);

    println!("Gizli sayı: {}", gizli_sayı);

    loop {
        println!("Tahmininizi girin.");

        let mut tahmin = String::new();

        io::stdin()
            .read_line(&mut tahmin)
            .expect("Veri okuma hatası!");

        let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı türü girin!");

        println!("Tahmininiz: {}", tahmin);

        // --snip--

        match tahmin.cmp(&gizli_sayı) {
            Ordering::Less => println!("Sayınız küçük!"),
            Ordering::Greater => println!("Sayınız büyük!"),
            Ordering::Equal => {
                println!("Kazandınız!");
                break;
            }
        }
    }
}

Kullanıcın doğru tahmini yaptığı ve "Bildiniz!" mesajının ekrana yazdırıldığı satırın ardına eklenen break ifadesi programın döngüden çıkmasını sağlar. Döngü main işlevinin son bölümü olduğundan döngüden çıkmak aynı zamanda programdan çıkmak anlamına da gelir.

Geçersiz Veri Girişlerini İşlemek

Oyunun davranışını daha da iyileştirebilmek amacıyla, sayısal olmayan bir değer alındığında programı çökertmek yerine, bu değerlerin yok sayılmasını ve kullanıcının doğru sayıyı bulana kadar tahmine devam etmesini sağlayalım. Bu iyileştirmeyi Örnek 2-5'te gösterildiği şekilde, String türündeki tahmin değişkenini, u32 türüne dönüştüren satırda değişiklik yaparak gerçekleştirebiliriz.

Dosya adı: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    let gizli_sayı = rand::thread_rng().gen_range(1..101);

    println!("Gizli sayı: {}", gizli_sayı);

    loop {
        println!("Tahmininizi girin.");

        let mut tahmin = String::new();

        // --snip--

        io::stdin()
            .read_line(&mut tahmin)
            .expect("Satır okuma hatası!");

        let tahmin: u32 = match tahmin.trim().parse() {
            Ok(sayı) => sayı,
            Err(_) => continue,
        };

        println!("Tahmininiz: {}", tahmin);

        // --snip--

        match tahmin.cmp(&gizli_sayı) {
            Ordering::Less => println!("Sayınız küçük!"),
            Ordering::Greater => println!("Sayınız büyük!!"),
            Ordering::Equal => {
                println!("Kazandınız!");
                break;
            }
        }
    }
}

Örnek 2-5: Sayı olmayan veriyle programı çökertmek yerine yeni bir tahmin istemek

expect çağrısının match ifadesiyle değiştirilmesi, programı çökerten hatadan düzgün şekilde işlenen hataya geçilmesini sağlar. Ayrıştırma işlemini gerçekleştiren parse metodunun bir Result türü döndürdüğünü ve bu türün OK veya Err varyantlarına sahip bir enum türü olduğunu unutmayın. Tıpkı cmp metodunun Ordering türünden döndürdüğü sonuçları işlediğimiz gibi burada da bir match ifadesi kullandıyoruz.

parse metodu dizgiyi bir sayıya düzgünce dönüştürebilirse, elde edilen sayıyı içeren bir Ok değeri döndürülür. Bu değer ilk dalın örüntüsüyle eşleştiğinde match ifadesi, parse ile oluşturulan sayi değerini alarak Ok değerinini içine yerleştirecek ve bu sayı yeni oluşturulan tahmin değişkeninde saklanacaktır.

Dizgi sayıya dönüştürülemiyorsa da, hata hakkında detaylı bilgi içeren Err değeri döndürülücektir. Bu değer match ifadesinin Ok(sayi) dalıylae değil, ikinci daldaki Err(_) kalıbıyla eşleşecektir. Bu kalıpta yer alan alt çizgi _ bize, içindeki değerlere bakılmaksızın Err varyantındaki tüm değerlerin bu dal ile eşleştirileceğini söylemektedir. Burası çalıştığında, döngünün bir sonraki yinelemesine atlanarak yeni bir tahmin verisi istemesini sağlayan continue ifadesi işletilecek, böylece parse metodunun karşılaşabileceği olası tüm hatalar yok sayılmış olacaktır.

Bu aşamada artık programımızdaki her şey beklendiği gibi çalışacaktır. Deneyelim:

$ cargo run
   Compiling tahmin_oyunu v0.1.0 (/home/rusdili/projeler/tahmin_oyunu)
    Finished dev [unoptimized + debuginfo] target(s) in 0.37s
     Running `target/debug/tahmin_oyunu`
Tuttuğum sayıyı tahmin edin!
Gizli sayı: 63
Tahmininizi girin.
20
Tahmininiz: 20
Sayınız küçük!
Tahmininizi girin.
99
Tahmininiz: 99
Sayınız büyük!
Tahmininizi girin.
çıkış
Tahmininizi girin.
63
Tahmininiz: 63
Kazandınız!

Mükemmel! Küçük ve son bir ince ayar daha yaptıktan sonra oyunu bitireceğiz. Test aşamasında gizli sayının ekrana yazdırılması önemli bir detayken, bunun sonuç aşamasında halen var olması oyunu mahvediyor. Bu durumu gizli_sayı değişkenini ekrana yazdıran println! satırını silerek düzeltelim. Örnek 2-6 kodun son halini gösterir.

Dosya adı: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Tuttuğum sayıyı tahmin edin!");

    let gizli_sayı = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Tahmininizi girin.");

        let mut tahmin = String::new();

        io::stdin()
            .read_line(&mut tahmin)
            .expect("Satır okuma hatası!");

            let tahmin: u32 = match tahmin.trim().parse() {
            Ok(sayı) => sayı,
            Err(_) => continue,
        };

        println!("Tahmininiz: {}", tahmin);

        match tahmin.cmp(&gizli_sayı) {
            Ordering::Less => println!("Sayınız küçük!"),
            Ordering::Greater => println!("Sayınız büyük!!"),
            Ordering::Equal => {
                println!("Kazandınız!");
                break;
            }
        }
    }
}

Örnek 2-6: Tahmin oyunu programının son hali

Özet

Tebrikler! Başarıyla çalışan bir sayı tahmin oyunu programladınız.

Bu proje, let, match, metotlar, ilişkili işlevler, harici sandıkların kullanılması gibi birçok Rust kavramını size tanıtmanın uygulamalı bir yoluydu. Kitabın ilerleyen bölümlerinde bu kavramlar hakkında daha çok şey öğreneceksiniz. 3. Bölümde değişkenler, veri türleri, işlevler gibi çoğu programlama dili tarafından kullanılan kavramları kapsanacak ve bunların Rust ile nasıl kullanıldığı gösterilecektir. 4. Bölümde ise Rust'ı diğer dillerden ayıran önemli bir özellik olan mülkiyet kavramı incelenecek, 5. Bölümde yapı ve metot söz dizimleri tartışılacak, 6. bölümdeyse enum türünün çalışması irdelenecektir.

Ortak Programlama Kavramları

Bu bölüm neredeyse tüm programlama dillerinde görülen kavramları ve bu kavramların Rust'ta nasıl çalıştığını kapsar. Pek çok dilin özünde birbiriyle ortak noktalar olduğundan bu bölümde sunulan kavramların hiçbiri Rust'a özgü değildir. Ancak biz bu kavramları Rust bağlamında tartışacak ve kullanımlarıyla ilgili kuralları açıklayacağız.

Özellikle değişkenler, temel türler, işlevler, yorum ve kontrol akışı hakkında bilgi edineceksiniz. Henüz yolun başındayken Rust programlama alt yapısını oluşturan bu bilgilerin öğrenilmesi size sağlam bir temel kazandıracaktır.

Anahtar Kelimeler

Diğer programlama dillerinde olduğu gibi Rust dilinde de, yalnızca dilin kullanabileceği bir dizi anahtar kelime vardır. Bu kelimeleri değişken veya işlev adlarında kullanamazsınız. Çoğu anahtar kelime özel bir anlam taşıdığından Rust programlarındaki çeşitli görevlerde bu kelimelerden faydalanacaksınız. Her ne kadar bu kelimelerden bazıları şu an için işlevsel değilmiş gibi görünse de, bunlar yakın gelecekte Rust'a eklenmesi planlanan işlevler için ayrılmıştır. Bu anahtar kelimelerin listesi kitabınızın Ek A bölümünde bulunmaktadır.

Değişkenler ve Değişkenlik

“Değerleri Değişkenlerde Saklamak” bölümünden hatırlayacağınız üzere Rust'ta değişkenler varsayılan olarak değişmez kabul edilmekteydi. Bu kabul kodlarınızı, Rust'ın getirdiği güvenlik ve eşzamanlılık avantajlarından yararlanacak şekilde yazmanızı teşvik eden birçok Rust yaklaşımından biridir. Ancak yine de değişkenlerinizi değişebilir yapma seçeğine her zaman sahipsiniz. Şimdi gelin Rust'ın sizi değişmezliğe nasıl ve neden yönlendirdiğini ve bazen bu değişemezlikten neden vazgeçmemiz gerektiğini birlikte inceleyelim.

Bir değişmez haldeki bir değişkene isim verilerek değer atandığında o değişkenin değerini artık değiştiremezsiniz. Bu konuya açıklık getirebilmek için projeler dizininde cargo new degiskenler komutunu kullanarak degiskenler adında yeni bir proje oluşturalım. Ardından degiskenler dizinindekki src/main.rs dosyasını açarak içindeki kodları şu an için derlenmeyen aşağıdaki kodlarla değiştirelim:

Dosya adı: src/main.rs

fn main() {
    let x = 5;
    println!("x'in değeri: {}", x);
    x = 6;
    println!("x'in değeri: {}", x);
}

Programınızı kaydedip cargo run komutuyla çalıştırdığınızda aşağıdakine gibi bir hata mesajı alacaksınız:

$ cargo run
   Compiling degiskenler v0.1.0 (/home/rustdili/projeler/degiskenler)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: consider making this binding mutable: `mut x`
3 |     println!("x'in değeri: {}", x);
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `no-listing-01-variables-are-immutable` due to previous error

Bu örnek, derleyicinin programlarınızdaki hataları bulmanıza nasıl yardımcı olacağını gösterir. Deneyimli Rust geliştiricilerinin bile karşılaşmaktan muaf olmadığı bu sinir bozucu görünen derleyici hataları, sizin kötü programcı olduğunuzu değil, programınızın yapması gereken şeyleri henüz güvenli bir şekilde gerçekleştiremediğini söylemektedir.

Hata mesajındaki cannot assign twice to immutable variable `x` uyarısı hatanın sebebi olarak değişmez olarak bildirilen x değişkenine ikinci kez değer atanamayacağını ancak bizim x değişkenine yeni bir değer atamaya çalıştığımızı bildirmektedir.

Değişmez olarak belirlenmiş bir değeri değiştirmeye çalışmak programda hatalara neden olabileceğinden böyle bir derleme zamanı hatası almamız önemlidir. Kodumuzun bir bölümünün bir değerin asla değişmeyeceği varsayımıyla hareket ettiği oysa başka bir bölümün bu değeri değiştirdiğini düşündüğünüzde kodun ilk bölümünün tasarlandığı gibi çalışmayacağı ortadadır. Bu şekilde ortaya çıkan hataların kaynağını saptamak, değişken değeri ara sıra değiştirildiğinde daha da çok zorlaşır. Rust'ta bir değerin değişmeyeceğini bildirdiğinizde derleyici bu değerin değişmeyeceğini garanti eder. Bu garanti bir kodu okur veya yazarken, değerlerin nerede ve nasıl değişeceğini takip etmenize gerek olmadığı anlamına gelmekte ve kodlarınızın kolayca anlaşılmasını sağlamaktadır.

Ancak değişebilirlik pratik kod yazmak gibi çok sayıda fayda sağlar. Değişkenler yalnızca varsayılan olarak değişmez olduklarından, tıpkı 2. Bölümde yaptığımız gibi önlerine mut kelimesini ekleyerek onları değişebilirsinizr. Anahtar kelime mut'un eklenmesi ileride bu kodu okuyacaklara, bu değişken değerinin kodun diğer bölümleri tarafından değiştirileceğini de gösterir.

Örneğin src/main.rs dosyasını aşağıdaki şekilde değiştirelim:

Dosya adı: src/main.rs

fn main() {
    let mut x = 5;
    println!("The value of x is: {}", x);
    x = 6;
    println!("The value of x is: {}", x);
}

Programı bu şekilde çalıştırdığımızda aşağıdaki çıktıyı elde ederiz:

$ cargo run
   Compiling degiskenler v0.1.0 (/home/rustdili/projeler/degiskenler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
     Running `target/debug/degiskenler`
x'in değeri: 5
x'in değeri: 6

mut'u kullanmakla x'e bağlı olan 5 değerinin 6 olarak değiştirilmesine izin vermiş oluruz. Hatalardan kaçınmanın yanı sıra verilmesi gereken başka tavizler de vardır. Örneğin, büyük yapılarla çalışırken mevcut bir örneği yerinde değiştirmek, yeni atanacak örneklerin kopyalanarak geri döndürülmesinden daha hızlı olabilir. Yahut küçük veri yapılarıyla çalışılırken yeni örnekler oluşturmak ve işlevsel programlama tarzından daha fazla yararlanmak anlaşılabilirliği arttıracağından, bu netlik uğruna performanstan ödün vermek göze alınabilecek bir tercih olabilir.

Sabitler

Değişmez değişkenler gibi sabitler de bir isme bağlı olan ve değiştirilmesine izin verilmeyen değerlerdir, ancak sabitler ve değişkenler arasında bazı farklılıklar bulunur.

Bunlardan ilki, mut anahtar kelimesinin sabitler ile kullanılmasına izin verilmez. Sabitler sadece varsayılan olarak değil daima değişmez olarak kabul edilirler. Sabitleri let anahtar sözcüğü yerine const anahtar sözcüğü kullanarak bildirebilirsiniz. Bu bildirim sırasında depoladıkları değer türünü açıkça belirtmelisiniz. Türler ve tür ek açıklamaları konusunu bir sonraki konumuz olan Veri Türleri bölümünde inceleyeceğimizden bu konunun ayrıntıları hakkında endişelenmeniz gerekmez. Şimdilik sabitleri bildirdiğiniz esnada türün açıkça belirtilmesi gerektiğini anımsamanız yeterlidir.

İkinci olarak sabitler, küresel kapsam dahil herhangi bir kapsamda bildirilebilirler. Bu da onların, kodun farklı bölümlerinde bilinen değerler olarak kullanılmasını sağlar.

Son olarak sabitler yalnızca bir işlev çağrısı sonucu olmayan sabit bir ifadeye veya çalışma zamanında hesaplanabilen başka bir değere ayarlanabilirler.

Aşağıda bir sabit örneği yer almaktadır:


#![allow(unused)]
fn main() {
const ÜÇ_SAATTEKİ_SANİYELER: u32 = 60 * 60 * 3;
}

ÜÇ_SAATTEKİ_SANİYELER adlı sabit üç saatin içinde kaç saniye olduğu bilgisini tutar. Ve değeri bir dakia içindeki saniye sayısı (60) ile bir saat içindeki dakika sayısı (60) ve saat sayısı olan (3)'ün çarpımına ayarlıdır. Rust'ın sabitler için adlandırma kuralı, kelimele aralarının alt çizgi ile ayrılması ve tüm harflerin büyük olarak kullanılmasıdır. Derleyici derleme zamanında bir dizi işlemi değerlendirebileceğinden, değerin doğrudan 10,800 olarak ayarlanması yerine, anlaşılması ve doğrulaması daha kolay olan biçimde yazılmasına izin verir. Sabit bildiriminde kullanılabilecek işlemler hakkında bilgilenmek için Rust Reference bölümündeki sabit değerlendirme bölümünü inceleyebilirsiniz.

Sabitler, bir programın çalıştığı süre boyunca, bildirildikleri kapsam dahilinde geçerlidir. Bu durum onları, uygulamanızın farklı bölümlerinden erişilebilen, bir oyuncunun alabileceği maksimum puan sayısı veya ışık hızı gibi belirgin değerlerin bilinmesi gerektiğinde oldukça kullanışlı bir seçenek haline getirir.

Programınız genelinde kullanılan sabit olarak kodlanmış değerleri sabit olarak adlandırmak, bu değerin anlamını ileride kodun bakımını üstlenecek geliştiricilere iletmede faydalıdır. Bununla birlikte sabit olarak kodlanmış bir değerin olası bir güncelleme durumunda tek bir yerden değiştirilecek olması kod bakımı için oldukça yararlıdır.

Gölgeleme

Bir önceki "Tahmin Sayısının Gizli Sayı ile Karşılaştırılması" bölümünden hatırlayacağınız üzere daha önce tanımlanmış bir değişken adıyla yeni bir değişken tanımlayabilirsiniz. Rust geliştiricileri tarafından önce tanımlanan değişkenin sonraki tarafından gölgelendiği ifade edilen bu durum, değişkenin kullanılması durumunda ikinci değişkene ait değerin elde edileceği anlamına gelmektedir. Aşağıdaki örnekte gösterildiği gibi, bir değişkeni aynı isimle ve let anahtar kelimesi tekrar kullanarak gölgeleyebiliriz.

Dosya adı: src/main.rs

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("Kapsam içindeki x'in değeri: {}", x);
    }

    println!("x'in değeri: {}", x);
}

Bu program ilk olarak x değişkenini 5 değerine bağlar. Ardından let x = ifadesini tekrarlanması sonucu x değişkenini, x'in orijinal değerini alıp üzerine 1 ekleyerek 6 olacak şekilde gölgeler. Ardından gelen iç kapsamda ise değer üçüncü kez gölgelenerek önceki değer 2 ile çarpılır ve x değişkeni 12 değerini almış olur. İç kapsamdan çıkıldığında içeride yapılmış olan gölgeleme de sona ereceğinden x yeniden 6 değerine döner. Program çalıştırıldığında aşağıdaki çıktıyı verecektir:

$ cargo run
   Compiling degiskenler v0.1.0 (/home/rustdili/projeler/degiskenler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.77s
     Running `target/debug/degiskenler`
Kapsam içindeki x'in değeri: 12
x'in değeri: 6

Gölgeleme, bir değişkeni mut olarak işaretlemekten farklıdır. Bir değişkeni let anahtar kelimesi kullanmadan yeniden atamaya çalışmak derleme zamanı hatasıyla sonuçlanır. Bir değer üzerinde let anahtar kelimesi kullanarak bazı dönüşümler yapabiliyor olsak bile, bu dönüşümler bittiğinde değişken yine bir değişmez olarak kalacaktır.

Gölgeleme ve mut arasındaki bir diğer fark ise let anahtar kelimesini tekrar kullanmakla etkili bir şekilde yeni bir değişken oluşturduğumuzdan, değerin türünü değiştirebilir ve değişkeni aynı adla kullanmaya devam edebiliriz. Örneğin kullanıcılara gösterilecek metinler arasında kaç boşluk olması gerektiğini soran ve girilen bu boşluk değerlerini sayı olarak saklamak istediğimizi düşünelim:

fn main() {
    let boşluk = "   ";     // Üç boşluk
    let boşluk = boşluk.len();
}

İlk boşluk değişkeni string (dizgi), alt satırdaki birinciyle aynı adı taşıyan fakat yepyeni bir değişken olan boşluk değişkeniyse tam sayı türünde olduğundan bu yapıya izin verilir. Gölgelemenin bu avantajı sayesinde boşluk_dizgi ve boşluk_sayı gibi farklı değişkenler oluşturmadan, boşluk adını tekrar kullanmakla bu sorunlardan kurtuluvermiş oluruz. Eğer bunun yerine mut anahtar sözcüğünü aşağıdaki gibi kullanmaya kalkarsak bir derleme zamanı hatası alırız.

fn main() {
    let mut boşluk = "   "; // Üç boşluk
    boşluk = boşluk.len();
}

Hata bize bir değişken türünün değiştirilmesine izin verilmediğini bildiriyor.

$ cargo run                                                                                                                       ✔ 
   Compiling degiskenler v0.1.0 (/home/rusdili/projeler/degiskenler)
error[E0308]: mismatched types
  --> src/main.rs:48:14
   |
47 |     let mut boşluk = "   "; // Üç boşluk
   |                      ----- expected due to this value
48 |     boşluk = boşluk.len();
   |              ^^^^^^^^^^^^ expected `&str`, found `usize`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `degiskenler` due to previous error

Artık değişkenlerin nasıl çalıştığını anladığımıza göre alabilecekleri veri türlerini inceleyebiliriz.

Veri Türleri

Rust'taki düm değerlerin belirli bir veri türüne ait olması Rust'a ne tür verilerin bildirildiği ve bu verilerin nasıl işleneceğini söyler. Bu başlıkta skaler ve bileşik olmak üzere iki veri türü alt kümesine odaklanacağız.

Rust'ın statik olarak yazılmış bir dil olduğunu ve tüm değişken türlerinin derleme sırasında biliniyor olması gerektiğini unutmayın. Derleyici genellikle değere ve onu nasıl kullandığımıza bağlı olarak kullanmak istediğimiz türü anlayabilir. Ancak çıkarsanabilecek farklı türlerin olması durumunda, kitabımızın 2. bölümünde yer alan "Tahmin Sayısının Gizli Sayı ile Karşılaştırılması" bölümünde String türünü sayısal bir türe dönüştürürken yaptığımız gibi tür ek açıklaması eklememiz gerekir:


#![allow(unused)]
fn main() {
let tahmin: u32 = "42".parse().expect("Lütfen bir sayı türü girin!");
}

Böyle bir ifadeye tür ek açıklaması eklenmezse Rust derleyicisi aşağıdaki gibi, kullanılmak istenen türün açıkça bildirilmesi gerektiğini söyleyen bir hata döndürecektir:

$ cargo run                                                                                                                   ✔
   Compiling degiskenler v0.1.0 (/home/rusdili/projeler/degiskenler)
warning: unused variable: `tahmin`
  --> src/main.rs:54:9
   |
54 |     let tahmin: u32 = "42".parse().expect("Lütfen bir sayı türü girin!");
   |         ^^^^^^ help: if this is intentional, prefix it with an underscore: `_tahmin`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: `degiskenler` (bin "degiskenler") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.72s
     Running `/home/rusdili/projeler/degiskenler/target/debug/degiskenler`

Diğer veri türleri için farklı tür ek açıklamaları göreceksiniz.

Skaler Veri Türleri

Bir skaler tür tek bir değeri temsil eder. Rust'ta dört ana skaler tür bulunur: Tamsayılar, kayan noktalı sayılar, boolean'lar ve karakterler. Diğer programlama dillerinden aşina olduğunuz bu türlerin Rust'ta nasıl çalıştığını inceleyelim.

Tamsayı Türleri

Tamsayılar kesirli bileşeni olmayan sayılardır. Hatırlarsanız kitabımızın 2. Bölümünde u32 türünde bir tamsayı kullanmıştık. Bu tür bildirimi, ilişkilendirildiği değerin bellekte 32 bitlik bir alanı kaplayan işaretsiz bir tamsayı olması gerektiğini belirtir. İşaretli tamsayılar i, işaretsiz tamsayılar ise u ön ekini alırlar. Tablo 3-1, Rust'ın yerleşik olan tamsayı türlerini göstermektedir. Tıpkı i16 örneğinde olduğu gibi. İşaretli ve İşaretsiz sütunlardaki her seçenek, bir tamsayı değerinin türünü bildirmek için kullanılabilir.

Tablo 3-1: Rust'ın Tamsayı Türleri

Uzunlukİşaretliİşaretsiz
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

Her varyant işaretli veya işaretsiz olabileceği gibi bit cinsinden belirli bir boyuta sahiptir. Varyantın işaretli olması sayının negatif değerler alabileceğini, işaretsiz olmasıysa sayının yalnızca pozitif olabileceği anlamına gelmektedir. Başka bir ifadeyle, sayının bir işaretli alması gerekip gerekmediğini işaretli, sayının sadece pozitif olacağını ve bir işaret ile gösterilmesi gerekmediğiniyse işaretsiz sayılar temsil eder. Bir sayıyı kağıda yazarken yaptığımız gibi, işaretin önemli olduğu hallerde sayıyı +, veya - olarak işaretlememize, pozitif olduğu hallerdeyse işaretsiz koymadan kullanmamıza benzer. İşaretli sayılar ikinin tümleyeni gösterimi kullanılarak depolanır.

Her işaretli varyant -(2ⁿ⁻¹) ile 2ⁿ⁻¹-1 arasındaki sayıları depolayabilir. Formüldeki n ise varyantın kullandığı bit sayısını gösterir. Bu bir i8 varyantının -(2) ile 2-1 arasındaki sayıları yani -128 ile 127 değerleri arasındaki sayıları depolayabileceğini gösterir. İşaretsiz varyantlar ise 0 ile 2ⁿ⁻¹ arasındaki sayıları saklayabildiklerinden, bir u8 varyantının 0 ile 2 - 1, yani 0 ile 255 arasındaki sayıları depolayabilirler.

Ek olarak boyutları ve kullanım türleri programın çalıştığı bilgisayar mimarisine bağlı olan isize ve usize türleri vardır. Bunlar 64 bit mimari kullanıyorsanız 64, 32 bit mimari kullanıyorsanız 32 bit olarak değerlendirilirler.

Tamsayı değişmezlerinizi tablo 3-2'de gösterilen biçimlerden herhangi biriyle yazabilirsiniz. Bayt değişmezi haricindeki tüm değişmez değerlerin, 57u8 gibi bir tür son ekine ve 1_000 örneğinde olduğu gibi görsel bir ayırıcı olarak _ kullanmanıza izin verdiğini unutmayın.

Tabl0 3-2: Rust'taki Tamsayı Değişmezleri

Sayısal DeğişmezÖrnek
Ondalık98_222
Onaltılık0xff
Sekizlik0o77
İkilik0b1111_0000
Bayt (sadeceu8)b'A'

Tam sayı türlerini seçerken kararsız kaldığınızı hissederseniz Rust'ın varsayılan türleri ile devam edebilirsiniz. Rust'ta tam sayılar için varsayılan tür i32 'dir. Bazı koleksiyonları indexlenmesi gerekiyorsa bunun için genellikle isize veya usize türü kullanılır.

Tamsayı Taşması

0 ile 255 arasında değerlere sahip olabilen u8 türünde bir değişkeniniz olduğunu varsayalım. Değişkeni bu aralığın dışında, örneğin 256 gibi türün tutabileceği en yüksek değerden fazla bir değere ayarlamaya çalışırsanız tamsayı taşması oluşacaktır. Rust'ın hata ayıklama (debug) modu derleme seçeneği, böyle bir davranışın oluşması halinde programı çalışma zamanında paniğe yol açacak şekilde tamsayı taşması denetimlerini içermektedir. Rust'ta panik terimi, bir hata nedeniyle programdan çıkıldığı anlamına gelir. Bu konuyu kitabımızın 9. Bölümünde "panic! ile Düzeltilemeyen Hatalar" başlığında yakından inceleyeceğiz.

Tamsayı taşması kontrolleri yayın modunda --release bayrağıyla yapılan derlemelerde gerçekleştirilmez. Ancak taşma oluşması halinde taşan değerler Rust tarafından ikinin tümleyeni yöntemiyle sarmalanarak, türün sahip olduğu en küçük değerden başlayıp ileriye doğru kaydırılır. Taşmanın u8 türünde olduğunu varsaydığımızda bu kaydırmalar, 256 değeri için 0'a, 257değeri içinse 1'e evrilir ve rakam yükseldikçe bu böyle devam eder. Program panikleyerek sonlanmaz fakat değişken olasılıkla beklenmeyen bir değere sahip olur. Tamsayı taşmaları için sarmalama yöntemine güvenmek bir hata olarak kabul edilir.

Taşma olasılıklarının açıkça yönetilebilmesi amacıyla standart kütüphanenin temel türlere özgü sağladığı Aşağıdaki gibi metotlardan yararlanabilirsiniz:

  • Tüm modları wrapping_add gibi wrapping_* metodlarıyla sarmalayın.
  • Taşmanın gerçekleşebileceği durumları checked_* metodlarıyla denetleyip None değeri döndürecek şekilde yönetin.
  • Taşmanın meydana gelip gelmediğini bir boolean değer döndürerek gösteren overflowing_* metodlarından yararlanın.
  • En yüksek ve en düşük değerleri doyurucu aritmetik işlemlerinden yararlanan saturating_* metodlarını kullanarak doyurun.

Kayan Noktalı Türler

Ondalık sayılar olarak bildiğimiz kayan noktalı sayılar için Rust'ta iki temel tür bulunur. Bunlar sırasıyla 32 bit boyutunda olan f32 ve 64 bit boyutunda olan f64 türleridir. Modern CPU'larda f32 ve f64 türleri aynı hızda çalıştığından Rust'ın kayan noktalı sayılar için varsayılanı daha yüksek bir hassasiyete sahip olan f64 türüdür. Kayan noktalı türlerin tümü işaretlidir.

Aşağıdaki örnek kayan noktalı sayıların işleyişini göstermektedir:

Dosya adı: src/main.rs

fn main() {
    let x = 2.0;        // Varsayılan tür: f64

    let y: f32 = 3.0;   // Tercihe bağlı tür: f32
}

Kayan noktalı sayılar IEEE-754 standardına göre temsil edilir. Buna göre f32 tek, f64 ise çift hassasiyetli türlerdir.

Sayısal İşlemler

Rust, tüm sayı türleri için; toplama, çıkarma, çarpma, bölme, kalan gibi ihtiyaç duyacağınız temel matematik işlemlerini destekler. Tam sayılar bölündüğünde bir altındaki en yakın sayıya yuvarlanır. Aşağıdaki örnek türlerin let ifadeleriyle nasıl kullanılabileceğini göstermektedir:

Dosya adı: src/main.rs

fn main() {
    // Toplama
    let toplam = 5 + 10;

    // Çıkarma
    let fark = 95.5 - 4.3;

    // Çarpma
    let sonuç = 4 * 30;

    // Bölme
    let bölüm = 56.7 / 32.2;
    let yuvarlama = 2 / 3; // Sonuç 0

    // Kalan
    let kalan = 43 % 5;
}

Her ifade metematiksel işleçler kullanarak ilgili değişkene atanacak olan benzersiz bir değeri hesaplar. Rust'ta yer alan matemetiksel işleçler bu kitabın EK B bölümünde listelenmektedir.

Boolean Türü

Çoğu programlama dilinde olduğu gibi Rust'taki boolean türü de true ve false olmak üzere bir baytlık iki olası değerden birine sahiptir. Bu tür Rust'ta bool olarak belirtilir. Örneğin:

Dosya adı: src/main.rs

fn main() {
    let t = true;

    let f: bool = false; // Tür ek açıklamasıyla
}

Boolean değerleri genellikle if gibi koşullu ifadelerle kullanılır. Bu ifadenin çalışma şeklini "Kontrol Akışı" bölümünde ele alacağız.

Karakter Türü

Rust'ın karakter türü dilin en temel alfabetik türüdür ve kullanılışı aşağıdaki gibi örneklenebilir.

Dosya adı: src/main.rs

fn main() {
    let c = 'z';
    let z = 'ℤ';
    let kalp_gozlü_kedi = '😻';
}

Çift Tırnak kullanan dizgi değişmezlerinin tersine char değişmezleri tek tırnakla bildirilir. Rust'ın char türü dört baytlık bir Unicode skaler değerini temsil ettiğinden ASCII karakter tablosunda bulunandan daha çok karakteri temsil eder. Aksanlı harfler, Çin, Japon, Kore, Türk dilindeki karakterler, emoji ve sıfır genişlik boşukların tamamı Rust'ta geçerli char değerlerdir. Unicode skaler değerleri U+0000 ile U+D7FF ve U+E000 ile U+10FFFF arasında değişir. Ancak "karakter" kavramı Unicode için gerçek bir kavram olmadığından, karakterin anlamına dair insan sezgisi ile Rust'taki karakterin anlamı tam olarak uyuşamayabilir. Bu konuyu 8. Bölümde "UTF-8 Kodlu Metni Dizgilerde Saklamak" bölümünde ayrıntılarıyla inceleyeceğiz.

Bileşik Veri Türleri

Bunlar çok sayıda değeri tek bir tür olarak gruplayabilen türlerdir. Rust'ta diziler ve çokuzlular olmak üzere iki temel bileşik tür bulunur.

Çokuzlu Türü

Çeşitli türlerden oluşan bir dizi değeri, tek bir bileşik tür halinde guruplamanın genel yoludur. Sabit uzunluktaki bu tür bir kez bildirildikten sonra büyüyüp küçülemez.

Parantez içinde virgülle ayrılmış değerler listesi yazarak oluşturulur ve çokuzlunun her konumu bir türü temsil eder. Bununla birlikte içerdiği farklı değerlerin aynı türden olmaları gerekmez. Örnekteki tür ek açıklamaları isteğe bağlı olarak eklenmiştir:

Dosya adı: src/main.rs

fn main() {
    let çokuz: (i32, f64, u8) = (500, 6.4, 1);
}

Çokuzlu tek bir bileşik öğe olarak kabul edildiğinden çokuz değişkeni tüm çokuzluya bağlanır. Bir çokuzluyu çözerek içerdiği her öğeye erişebilmek için örüntü eşlemeyi kullanabiliriz.

Dosya adı: src/main.rs

fn main() {
    let çokuz = (500, 6.4, 1);

    let (x, y, z) = çokuz;

    println!("Değişken y değeri: {}", y);
}

Bu program ilk önce bir çokuzlu oluşturarak onu çokuz değişkenine bağlar. Ardından çokuz değişkeni alınıp, x, y ve z adlarında üç ayrı değişkene dönüştürüleceği let ifadesi kullanan bir modelden yararlanılır. Bu işleme, bir çokuzluyu alarak üç parçaya ayırıp, her parçayı ayrı bir değişkene dönüştürmesinden dolayı çözme, yıkma anlamına gelen destructuring adı verilir. Nihayetinde program y değerinin karşılığı olan 6.4'ü ekrana yazdırmış olur.

Bu yöntemine ek olarak çokuzlunun öğelerine isminden hemen sonra bir (.) nokta ve öğe dizin numarası yazarak doğrudan erişebiliriz. Örneğin:

Dosya adı: src/main.rs

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let beş_yüz = x.0;

    let altı_nokta_dört = x.1;

    let bir = x.2;
}

Bu program x adında bir çokuzlu oluştur ve ardından her öğenin dizin numarasını kullanarak onlardan yeni değişkenler üretir. Bir çokuzlunun dizin numarası çoğu programlama dilinde olduğu gibi 0'dan başlar.

Hiç bir değere sahip olmayan () boş bir çokuzlu yalnızca bir değere sahip özel bir türdür ve () şeklinde yazılabilir. Bu türe birim türü değerine ise birim değer adı verilir. Hiç bir değer döndürmeyen ifadeler örtük olarak birim değer döndür.

Dizi Türü

Çok sayıda değerden oluşan bir koleksiyona sahip olmanın başka yolu da dizilerden yararlanmaktır. Çokuzlunun tersine bir dizinin her elemanı aynı türden olmalıdır. Bazı dillerdeki dizilerin aksine, Rust'taki dizilerin uzunluğu sabittir.

Bir dizinin değerlerini köşeli parantezler içine ve virgülle ayrılmış liste olarak yazarız.

Dosya adı: src/main.rs

fn main() {
    let a = [1, 2, 3, 4, 5];
}

Diziler, verilerinizin öbek yerine stack (Bundan böyle yığın olarak bahsedilecektir) üzerinde depolanmasını(Yığın ve öbek konusunu 4. bölümde inceleyeceğiz) veya daima belli sayıda öğelere sahip olmak istediğiniz hallerde yararlıdır. Yine de diziler, vektörler kadar esnek değildir. Standart kitaplık tarafından sağlanan vektörler diziyle benzeşen ancak boyutları değişebilen koleksiyon türleridir. Bunlardan hangisini kullanacağınızdan emin olamadığınız durumlarda olasılıkla bir vöktör türüne ihtiyacınız vardır. Vektörleri 8. bölümde tartışıyor olacağız.

Diziler eleman sayısının değişmeyeceği bilinen durumlarda kullanışlıdır. Eğer ayların isimlerini kullanan bir kod yazıyor olsaydınız başka bir ayın girip çıkması mümkün olmayan ve daima 12 elemandan oluşan bir listeniz olacağından vektör yerine dizi kullanmayı tercih ederdiniz.


#![allow(unused)]
fn main() {
let aylar = ["Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran",
             "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"];
}

Bir dizinin türü köşeli parantez kullanılarak yazılır. Bu parantezin içinde aşağıdaki örneğe benzer biçimde, önce öğelerin türü, sonra noktalı virgül ve ardından dizide depolanacak eleman adedi belirtilir:


#![allow(unused)]
fn main() {
let a: [i32; 5] = [1, 2, 3, 4, 5];
}

Parantez içindeki i32 depolanacak elemanların türünü, noktalı virgülden sonraki 5 rakamı ise dizinin beş öğeden oluşacağını gösterir.

Aşağıda gösterildiği gibi aynı değerlerden oluşan bir diziyi köşeli parantezlerin içine önce başlangıç değerini, ardından bir noktalı virgül ve son olarak dizide bu değerden kaç tane olacağını belirten uzunluk değerini girerek bildirebilirsiniz:


#![allow(unused)]
fn main() {
let a = [3; 5];
}

Örnekteki a dizisi değeri 3 olan 5 öğeden oluşmaktadır. Bu gösterim let a = [3, 3, 3, 3, 3]; şeklinde yazılacak kodun aynısı olup daha kısa ve özlü biçimdeki ifadesidir.

Dizi Öğelerine Erişim

Dizi, yığın üzerinde depolanan tek bir bellek bloğudur. Dizi öğelerine aşağıda gösterildiği gibi dizin numaralarını kullanarak erişebilirsiniz:

Dosya adı: src/main.rs

fn main() {
    let a = [1, 2, 3, 4, 5];

    let birinci = a[0];
    let ikinci  = a[1];
}

örnekteki birinci değişkeni, dizinin indeks başlangıcı olan [0] pozisyonunda 1 değeri bulunduğundan 1 değerini, ikinciadındaki değişkense [1] pozisyonunda 2 değeri bulunduğundan 2 değerini alacaktır.

Geçersiz Dizi Öğesine Erişmek

Dizi sınırları dışında kalan bir öğe numarasına erişmek isterseniz ne olur? 2. Bölümdeki sayı tahmin oyununa benzer bir kod kullanan aşağıdaki örneği kullanıcıdan bir dizin numarası alacak şekilde değiştirdiğimizi varsayalım:

Dosya adı: src/main.rs

use std::io;

fn main() {
    let a = [1, 2, 3, 4, 5];

    println!("Lütfen bir dizin numarası giriniz:");

    let mut dizin = String::new();

    io::stdin()
        .read_line(&mut dizin)
        .expect("Satır okunamadı");

    let dizin: usize = dizin
        .trim()
        .parse()
        .expect("Girilen dizin numarası bir sayı olmalıdır.");

    let öğe = a[dizin];

    println!(
        "dizin {}'de bulunan öğe değeri: {}",
        dizin, öğe
    );
}

Bu kod cargo run komutuyla çalıştırdığınızda başarıyla derlenecektir. Program çalıştırdığınızda sizden istenilen dizin numarasını 0, 1, 2, 3, 4 olarak girerseniz o dizin numarasına karşılık gelen değer yazdırılır. Fakat dizi boyutunu aşan 5 veya 10 gibi bir değer girerseniz aşağıdaki gibi bir çıktı alırsınız:

thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 5', src/main.rs:19:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Program dizin numarasında geçersiz bir değer kullanıldığında bir çalışma zamanı hatası ve hatayı içeren bir mesaj eşliğinde sonlanarak en alt satırdaki println! ifadesini yürütmez. Bir öğeye dizin numarası kullanarak erişmeye çalıştığınızda Rust, belirtilen dizin numarasının dizi uzunluğundan daha az olup olmadığını kontrol eder. Dizin numarası dizi uzunluğuna eşit veya büyükse programın çalıştırılması panik yoluyla sonlandırılır. Programın çalışması sırasında elde edilecek verilerin kontrol edilmesini gerektiren senaryolarda derleyicinin, kullanıcı tarafından hangi dizin numarasının girildiğine dair bir fikri olamayacağından bu tür kontrol ve denetimlerin çalışma zamanında yapılması gerekir.

Bu senaryo Rust'ın bellek güvenliği ilkelerinin uygulamadaki örneğidir. Böyle bir denetim pekçok düşük seviyeli programlama dilinde genellikle yapılmadığından hatalı bir dizin numarasıyla yapılan işlem sonucu geçersiz belleğe erişilir. Ancak Rust, bellek erişimine izin vermek yerine çalışmayı durdurarak sizi bu tür hatalara karşı korur. Rust'ın hata işleme yöntemlerine 9. Bölümde değineceğiz.

İşlevler

İşlevler Rust kodlarında yaygın olarak kullanılır. Dildeki en önemli işlevlerden biri olan ve programın giriş noktasını oluşturan main ile, yeni bir işlev bildirmeye yarayan fn anahtar sözcüğünü daha önce görmüştünüz.

Rust geleneksel olarak değişken ve işlev isimlerinde küçük harflerden oluşan ve ayrı kelimelerin alt çizgi ile birbirine bağlandığı snake_case tarzını kullanmaktadır.

Aşağıdaki programda bir işlev tanımı örneklenmektedir:

Dosya adı: src/main.rs

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

    başka_işlev();
}

fn başka_işlev() {
    println!("Bir başka işlev.");
}

Rust'ta bir işlevi fn anahtar kelimesi ardından işlev adı ve bir parantez seti ile tanımlarız. Süslü parantezlerin konumu derleyiciye işlevin nerede başlayıp nerede bittiğini bildirir.

Halihazırda tanımlı olan bir işlev ise adı ve arkasına gelen parantez seti ile çağırılır. Örneğimizde başka_işlev zaten tanımlı olduğundan main işlevi içinden çağırılabilmektedir. Kaynak kodumuzdaki başka_işlev'i main işlevinden sonra tanımladığımıza dikkat edin. Rust işlevlerin nerede tanımlandığıyla ilgilenmediğinden dilerseniz işlevlerinizi main işlevinden önce de tanımlayabilirsiniz.

İşlevlere daha yakından bakabilmek için cargo new komutu kullanarak projeler dizininde islevler adlı yeni bir proje başlatın. Arkasından başka_işlev örneğini src/main.rs içine alarak kodunuzu çalıştırın. Ekranınızda aşağıdaki çıktıyı görnelisiniz:

 $ cargo run                                                                                                                          ✔
   Compiling islevler v0.1.0 (/home/rusdili/projeler/islevler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.67s
     Running `target/debug/islevler`
Merhaba dünya!
Bir başka işlev.

Satırlar main işlevinde göründüğü sırayla işletilir. İlk olarak "Merhaba dünya!" mesajı, arkasından başka_işlev çağrısının ürettiği "Bir başka işlev." mesajı yazdırılır.

Parametreler

İşlevleri, işlev imzasına ait özel değişkenler olan parametreler ile birlikte tanımlayabiliriz. Bir işlevde parametreler bulunuyorsa bu parametrelere somut değerler iletebilirsiniz. İşlevlere parametre olarak iletilen somut değerler argüman aolarak adlandırılır. Fakat insanlar konuşmalarında bu kavramları kullanırken, işlev tanımındaki değişkenleri anlatan parametre yerine argüman, işlev çağrısı esnasında iletilen somut değerleri temsil eden argüman yerine parametre olarak bahsetmekte veya tam tersi biçimde birbirinin yerine geçirerek kullanma eğilimindedirler.

Aşağıda başka_işlev'in şimdiki sürümüne bir parametre ekliyoruz:

Dosya adı: src/main.rs

fn main() {
    başka_işlev(5);
}

fn başka_işlev(x: i32)  {
     println!("X'in değeri: {}", x);
}

Programı çalıştırdığınızda aşağıdaki çıktıyı alıyor olmalısınız:

$ cargo run                                                                                                                          ✔ 
   Compiling islevler v0.1.0 (/home/rusdili/projeler/islevler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/islevler`
X'in değeri: 5

Artık başka_işlev'in x adında ve i32 türünde bir parametresi vardır. Bu işleve 5 değeri iletildiğinde println! makrosu bu değeri biçimlendirme dizgisindeki süslü parentezlerin olduğu yere koyar.

İşlev imzalarında bulunan her parametrenin türü mutlaka bildirilmelidir. Bu Rust tasarlanırken alınan bilinçli bir karardır. İşlev tanımlarında tür bildirimi zorunluluğu, derleyicinin kullanılacak türü kodun başka bir yerinde kullanılmadan anlamasını sağlar.

Tanımlanan parametre sayısı birden fazlaysa bildirimlerin arası aşağıdaki gibi virgül ile ayrılmalıdır:

Dosya adı: src/main.rs

fn main() {
    etiket_değerlerini_yazdır(5, 'h');
}

fn etiket_değerlerini_yazdır(değer: i32, birim: char) {
    println!("Etiket değerleri: {}{}", değer, birim);
}

Bu örnek iki parametresi bulunan etiket_değerlerini_yazdır adlında bir işlev oluşturur. İşlevin değer adındaki ilk parametresi i32, birim adındaki ikinci parametresiyse char türündedir. Bu işlev değer ve birim verilerini içeren bir metin yazdırır.

Bu kodu islevler adlı projenizin src/main.rs dosyasında bulunan bir önceki kodla değiştirdikten sonra cargo run komutunu kullanarak çalıştırın:

$ cargo run                                                                                                      ✔ 
   Compiling islevler v0.1.0 (/home/rusdili/projeler/islevler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/islevler`
Etiket değerleri: 5h

İşlevi değer verisi için 5, birim verisi için h ile çağırdığımızdan dolayı program çıktısı bu değerleri içermektedir.

Deyimler ve İfadeler

İşlev gövdeleri isteğe bağlı olarak bir ifadeyle biten deyimlerden oluşur. Her ne kadar şu ana kadar gördüğünüz işlevler bitiş ifadesi içermiyor olsa da deyimin bir parçası olan ifadeyle karşılaştınız. Rust ifade tabanlı bir dil olduğundan bu ayrıntının anlaşılması önemlidir. Bu ayrım diğer dillerde olmadığından deyim ve ifadenin ne olduğuna ve farklarının işlev gövdelerini nasıl etkilediğini inceleyelim.

Deyimler bazı eylemleri gerçekleştiren ve bir değer döndürmeyen talimatlarken, ifadeler sonuç olarak bir değer döndürürler.

Zaten deyim ve ifadeleri daha önce kullanmıştık. Örneğin let anahtar sözcüğüyle değişken oluşturarak ona değer atamak ve Örnek 3-1'deki let y = 6 talimatı birer deyimdir.

Dosya adı: src/main.rs

fn main() {
    let y = 6;
}

Örnek 3-1: Deyimden içeren bir main işlevi

Tıpkı bu örneğin tamamı gibi işlev tanımları da kendi içinde birer deyimdir.

Aşağıdaki kodda yapıldığı gibi let deyimini bir başka değişkene atamaya kalktığınızda, deyimler değer döndürmedikllerinden hata almanız kaçınılmazdır:

Dosya adı: src/main.rs

fn main() {
    let x = (let y = 6);
}

Bu programı çalıştırdığınızda aşağıdaki gibi bir hata alacaksınız:

cargo run                                                                                                                          ✔ 
   Compiling islevler v0.1.0 (/home/rusdili/projeler/islevler)
error: expected expression, found statement (`let`)
  --> src/main.rs:41:14
   |
41 |     let x = (let y = 6);
   |              ^^^^^^^^^
   |
   = note: variable declaration using `let` is a statement

error[E0658]: `let` expressions in this position are experimental
  --> src/main.rs:41:14
   |
41 |     let x = (let y = 6);
   |              ^^^^^^^^^
   |
   = note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
   = help: you can write `matches!(<expr>, <pattern>)` instead of `let <pattern> = <expr>`

warning: unnecessary parentheses around assigned value
  --> src/main.rs:41:13
   |
41 |     let x = (let y = 6);
   |             ^         ^
   |
   = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
   |
41 -     let x = (let y = 6);
41 +     let x = let y = 6;
   | 

For more information about this error, try `rustc --explain E0658`.
warning: `islevler` (bin "islevler") generated 1 warning
error: could not compile `islevler` due to 2 previous errors; 1 warning emitted

Burada let y = 6 deyimi bir değer döndürmeyeceğinden x'in bağlanacağı bir değer yoktur. Bu durum böyle bir atamanın atanan değeri döndürdüğü C ve Ruby gibi dillerden farklıdır. Bahsedilen dillerde x = y = 6 şeklide bir talimatla hem x hem de y değişkenlerine 6 değerini atayabilirsiniz. Ancak Rust'ta durum böyle değildir.

İfadeler ise değer olarak hesaplanır ve Rust'ta yazacağınız kodların çoğunluğu ifadelerden oluşacaktır. Bunu 11 sonucunu veren 5 + 6 matematiksel işlemiymiş gibi düşünün. İfadeler deyimlerin bir parçası olabilir. Örnek 3-1'de yer alan let y = 6 deyimindeki 6'nın, işletildiğinde 6 olarak değerlendirilmesi gibi işlev çağrıları da birer ifadedir. Tıpkı makro çağrılarının birer ifade olması gibi süslü parantezlerle oluşturulan kapsam blokları da birer ifadedir. Örneğin:

Dosya adı: src/main.rs

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("Y'nin değeri: {}", y);
}

Burada:

{
    let x = 3;
    x + 1
}

let deyiminin parçası olan ve 4 olarak değerlendirilen blok bir ifadedir. Bu değer let deyiminin bir parçası olduğundan y değişkenine bağlanır. Şimdiye kadar gördüğünüz çoğu satırın aksine x + 1 satırının sonunda noktalı virgülün olmadığına dikkat edin. İfadeler noktalı virgül ile sonlanmaz. Eğer bu ifadenin sonuna noktalı virgül eklerseniz onu değer döndürmeyen bir deyime dönüştürürsünüz. Bunu, bir sonraki konuda dönüş değerleri ve ifadeleri incelerken aklınızda bulundurun.

Değer Döndüren İşlevler

İşlevler kendilerini çağıran koda değer döndürebilirler. Dönüş değerleri isimlendilmez fakat türleri bir oku (->) takiben bildirilir. Rust'ta işlevin dönüş değeri, işlev gövdesindeki son ifadenin değeriyle aynıdır. İşlevden erken çıkabilmek için dönüş değeri eşliğinde return anahtar sözcüğünü kullanabilirsiniz, ancak pekçok işlev son ifadeyi örtük biçimde döndürür. Aşağıda değer döndüren bir işlev örneği verilmektedir:

Dosya adı: src/main.rs

fn beş() -> i32 {
    5
}

fn main() {
    let x = beş();

    println!("X'in değeri: {}", x);
}

Beş işlevinde 5 rakamı hariç hiçbir işlev çağrısı, makro ve let deyimi yoktur. Ve bu Rust'ta tamamen geçerli bir işlevdir. İşlev dönüş türünün -> i32 olarak belirtildiğine dikkat edin. Bu kodu çalıştırdığınızda aşağıdaki gibi çıktı üretmelidir:

$ cargo run                                                                                                                          ✔ 
   Compiling islevler v0.1.0 (/home/rusdili/projeler/islevler)
    Finished dev [unoptimized + debuginfo] target(s) in 1.25s
     Running `target/debug/islevler`
X'in değeri: 5

İşlevin dönüş değeri 5 olduğundan dönüş türü de i32 olarak ayarlanmıştır. İşlevi dikkatle incelediğimizde iki şeyle karşılaşırız. İlki olarak let x = beş(); satırı, x değişkenini başlatmak için işlevin dönüş değerinden yararlandığımızı anlatır. Beş işlevi 5 değerini döndürdüğünden o satır aşağıdakiyle aynı anlama gelir.


#![allow(unused)]
fn main() {
let x = 5;
}

İkinci olarak, beş işlevi parametresiz olmamasına rağmen döndürülecek değerin türünü tanımlar. Bununla birlikte işlev gövdesi döndürmek istediğimiz değeri ifade ettiğinden noktalı virgül olmadan tek bir 5'ten oluşur.

Başka bir örneği inceleyelim:

Dosya adı: src/main.rs

fn main() {
    let x = artı_bir(5);

    println!("X'in değeri: {}", x);
}

fn artı_bir(x: i32) -> i32 {
    x + 1
}

Bu kodu çalıştırdığınızda X'in değeri: 6 sonucunu yazdıracaktır. Ama x + 1'in bulunduğu satır sonuna onu bir ifadeden deyime çeviren noktalı virgül eklerseniz hata ile karşılaşırsınız:

Dosya adı: src/main.rs

fn main() {
    let x = artı_bir(5);

    println!("X'in değeri: {}", x);
}

fn artı_bir(x: i32) -> i32 {
    x + 1;
}

Bu kod derlendiğinde aşağıdaki benzer şekilde hata üretecektir:

$ cargo run                                                                                                                          ✔ 
   Compiling islevler v0.1.0 (/home/rusdili/projeler/islevler)
error[E0308]: mismatched types
  --> src/main.rs:84:24
   |
84 | fn artı_bir(x: i32) -> i32 {
   |    --------            ^^^ expected `i32`, found `()`
   |    |
   |    implicitly returns `()` as its body has no tail or `return` expression
85 |     x + 1;
   |          - help: consider removing this semicolon

For more information about this error, try `rustc --explain E0308`.

Hatayı özetleyen “mismatched types” (uyumsuz türler) mesajı bize bu kodunun temel sorununu göstermektedir. artı_bir işlev tanımı i32 türünde bir değer döndürüleceğini bildirirken artık bir deyim olan ve birim türü () olarak temsil edilen satır bir değer olarak değerlendirilemez. Bu nedenle işlev tanımıyla çelişen ve bir hatayla sonuçlabilecek hiçbir şey döndürülmez. Rust hata raporunda noktalı virgülün kaldırılmasını öneren bir mesajla sorunun çözülmesine yardım eder.

Yorumlar

Tüm geliştiriciler kodlarının kolay anlaşılmasını isterler. Ancak bunu sağlamak için bazen kodun içine ek açıklamalar yazmak gerekir. Böyle durumlarda kaynak kodun içinde, derleyicinin görmezden geleceği fakat kodu okuyanlar için faydalı olabilecek yorumlar bırakılır.

Basit bir yorum örneği:


#![allow(unused)]
fn main() {
// Merhaba dünya!
}

Rust'ta klasik yorumlar iki eğik çizgiyle başlar ve satır sonuna kadar devam eder. Bir satırdan daha uzun süren yorumlar için örnekte gösterildiği gibi her satır için eğik // çizgi eklemeniz gerekir:


#![allow(unused)]
fn main() {
// Burada karmaşık bir şey yok. Bu kod sadece bir satıra sığmayacak kadar uzun olan
// ve alt satıra geçen yorum satırlarını örneklemek için oluşturulmuş bir kod parçasıdır.
// Kolaylıkla anlaşılabilir olduğunu umuyorum...
}

Yorumlar kodun bulunduğu satır sonuna da eklenebilirler:

Dosya adı: src/main.rs

fn main() {
    let şanslı_numaram = 7; // Bugün kendimi şanslı hissediyorum!
}

Ancak bazen açıklamalanın aşağıda örneklendiği gibi açıkladığı kodun üzerindeki satırda yer aldığını göreceksiniz:

Dosya adı: src/main.rs

fn main() {
    // Bugün kendimi şanslı hissediyorum!
    let şanslı_numaram = 7;
}

Rust'ın ayrıca "Bir Sandığı Cretes.io'da Yayınlamak" adlı 14. Bölümünde inceleyeceğimiz kodun belgelenmesini sağlayan başka bir yorum türü daha bulunmaktadır.

Kontrol Akışı

Bir kodun doğruluğuna bağlı olarak kodun başka bir bölümünün çalıştırılması veya bazı kodların bir koşul doğru olduğu sürece çalıştırılması çoğu programlama dilinin temel yapı taşlarıdır. Rust'ta kod yürütme akışını kontrol edebilmenizi sağlayan en yaygın kontrol yapıları if ifadeleri ve loop döngüleridir.

if İfadeleri

If ifadesi kodunuzu koşullara göre bölerek yürütmenize olanak sağlar. Bir koşul belirleyip ardından "Bu şart sağlanırsa şu kod bloğunu çalıştırın, koşul sağlanmıyorsa çalıştırmayın." demeye benzer.

If ifadesini daha iyi kavrayabilmek için projeler dizininde dallar adında yeni bir proje oluşturup src/main dosyasına aşağıdaki kodları ekleyin:

Dosya adı: src/main.rs

fn main() {
    let sayı = 3;

    if sayı < 5 {
        println!("Koşul doğru.");
    } else {
        println!("Koşul yanlış!");
    }
}

Tüm if ifadeleri, if anahtar kelimesiyle başlar ve bunu bir koşul takip eder. Örneğimizdeki koşul sayı değişkeninin 5'ten küçük olup olmadığını kontrol eder. Koşulun true (doğru) olması durumunda yürütülecek kod bloğu koşulun hemen ardından eklenen süslü parantezler içine yerleştirilir. If ifadesinin koşulunu denetleyen kod blokları, kitabın 2. Bölümündeki Tahmin Sayısının Gizli Sayı ile Karşılaştırılması konusunda yer alan eşleme ifadesinde olduğu gibi bazen kol bazen dal olarak adlandırılabilir.

Dilerseniz örnekte yaptığımız gibi, koşulun false (yanlış) olması halinde alternatif bir kod bloğu olarak işletilecek bir else ifadesini de kodunuza ekleyebilirsiniz. Eğer koşul yanlış ve bir else ifadesi bildirilmemişse program if bloğunu geçerek sonraki kod parçasına atlayacaktır.

Kodu çalıştırdığınızda aşağıdaki çıktıyı görmelisiniz:

$ cargo run                                                                                                                            ✔
   Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
    Finished dev [unoptimized + debuginfo] target(s) in 1.82s
     Running `target/debug/dallar`
Koşul doğru.

Ne olacağını görmek için sayı değerini, koşulu false yapacak bir değerle değiştirelim:

fn main() {
    let sayı = 7;

    if sayı < 5 {
        println!("Koşul doğru.");
    } else {
        println!("Koşul yanlış!");
    }
}

Ve programı yeniden çalıştırıp çıktıyı inceleyin:

$ cargo run                                                                                                                            ✔ 
   Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/dallar`
Koşul yanlış!

Bu koddaki koşulun bir bool olması gerektiğini belirtmekte yarar var. Koşulun bool olmaması halinde hata ile karşılaşırız. Örnekteki kodu çalıştırmayı deneyin:

Dosya adı: src/main.rs

fn main() {
    let sayı = 3;

    if number {
        println!("Sayı üç");
    }
}

Artık if koşulu 3 değerine ayarlanmış olduğundan Rust bir hata döndürecektir:

$ cargo run
   Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
error[E0308]: mismatched types
  --> src/main.rs:29:8
   |
29 |     if sayı {
   |        ^^^^ expected `bool`, found integer

For more information about this error, try `rustc --explain E0308`.
error: could not compile `dallar` due to previous error

Bu hata bize, Rust'ın bool türünde bir değer beklediğini ancak tam sayı türünde değer aldığını gösterir. Ruby veya Javascript gibi dillerin tersine Rust, boolean olmayan türleri otomatik olarak boolean türüne dönüştürmeye kalkışmaz. Açık olmanız ve if koşulunun daima boolean olmasını sağlamanız gerekir. Örneğin sayı 0 olmadıkça if kod bloğunun yürütülmesini istiyorsak, if ifadesini aşağıdaki gibi değiştirebiliriz:

Dosya adı: src/main.rs

fn main() {
    let sayı = 3;

    if sayı != 0 {
        println!("Bu sayı sıfır değil!");
    }
}

Bu kod çalıştırıldığında ekrana Bu sayı sıfır değil! mesajını yazdıracaktır.

else if ile Koşulları İşlemek

Bir else if ifadesinde if ve else kelimelerini birleştirerek çok sayıda koşulu denetleyebilirsiniz:

Dosya adı: src/main.rs

fn main() {
    let sayı = 6;

    if sayı % 4 == 0 {
        println!("Sayı 4' e kalansız bölünebilir.");
    } else if sayı % 3 == 0 {
        println!("Sayı 3' e kalansız bölünebilir.");
    } else if sayı % 2 == 0 {
        println!("Sayı 2' ye kalansız bölünebilir.");
    } else {
        println!("Sayı 4, 3 veya 2'ye kalansız bölünemez!");
    }
}

Bu programın gidebileceği dört olası yol vardır. Progamı çalıştırdığınızda aşağıdaki çıktıyı görmelisiniz:

$ cargo run
   Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/dallar`
Sayı 3' e kalansız bölünebilir.

Program yürütüldüğünde if ifadelerinin her birini sırayla kontrol edecek ve bulduğu ilk doğru koşulu işletecektir. 6 sayısının 2'ye kalansız bölünüyor olmasına rağmen, çıktıda Sayı 2' ye kalansız bölünebilir. mesajını veya else bloğunda yer alan Sayı 4, 3 veya 2'ye kalansız bölünemez! mesajını görmediğimize dikkat edin. Bunun nedeni Rust'ın kontrol sırasındaki ilk doğru koşulu bularak onu işletmesi ve diğer koşulların doğu olup olmamasıyla ilgilenmemesidir.

Çok sayıda else if ifadesi kullanmak kodunuzu karıştırabilir. Gereğinden fazla else if ifadesi kullandığınızı düşünüyorsanız kodunuzu yeniden düzenlemelisiniz. Kitabın 6. bölümünde böyle durumlarda kullanabileceğiniz güçlü bir dallanma yapısına sahip match (eşleme) adlı bir yapı anlatılır.

Bir let Deyiminde if Kullanmak

Örnek 3-2'de olduğu gibi if'in bir ifade olması, sonucunun herhangi bir değişkene atanmak üzere let deyiminin sağ tarafında kullanabilmesni sağlar.

Dosya adı: src/main.rs

fn main() {
    let koşul = true;
    
    let sayı = if koşul {5} else {6};
    
    println!("Sayının değeri : {}", sayı);
}

Örnek 3-2: Bir if ifadesi sonucunu değişkene atamak.

Sayı değişkeni if ifadesinin sonucuna göre oluşan bir değere bağlanacaktır. Bu kodu çalıştırdığınızda aşağıdaki çıktıyı elde edeceksiniz:

$ cargo run
   Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
    Finished dev [unoptimized + debuginfo] target(s) in 1.36s
     Running `target/debug/dallar`
Sayının değeri: 5

Kod bloklarının, içlerinde bulunan son ifadeyi değerlendirdiğini ve sayıların da birer ifade olduğunu unutmayın. Bizim durumumuzda tüm if ifadesinin değeri yürütülecek olan kod bloğunun değerine bağlıdır. Bu da, if ifadesindeki sonuç üretme potansiyeline sahip her dalın aynı türden olması gerektiği anlamına gelmektedir. Örnek 3-2'de bulunan if ve else dallarının her biri i32 türünde birer tam sayıdır. Aşağıdaki örnekten de anlaşılacağı gibi, türlerin uyumsuz olması hata alınmasına neden olur:

Dosya adı: src/main.rs

fn main() {
    let koşul = true;

    let sayı = if koşul {5} else { "Altı" };

    println!("Sayının değeri: {}", sayı);
}

Kodu derlemeye çalıştığımızda if ve else kollarının uyumsuz türlerden oluştuğu ve bu hatanın hangi satırda bulunduğunu gösteren bir hata raporuyla karşılaşırız:

$ cargo run
   Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:70:36
   |
70 |     let sayı = if koşul {5} else { "Altı" };
   |                          -         ^^^^^^ expected integer, found `&str`
   |                          |
   |                          expected because of this

For more information about this error, try `rustc --explain E0308`.
error: could not compile `dallar` due to previous error

If bloğundaki ifade tam sayı olarak değerlendirilirken else bloğundaki ifadeyse dizgi olarak olarak değerlendirilecektir. Değişkenlerin aynı türden olması ve sayı değişkeni türünün Rust tarafından derleme zamanında kesinlikle biliniyor olması gerektiğinden bu kod işe yaramaz. Sayı türünün derleme zamanında biliniyor olması, bu değişkenin kullanıldığı her yerde, derleyici tarafından türünün doğru ve geçerli olduğunun garantilenmesini sağlar. Eğer sayı değişkeninin türü sadece çalışma zamanında belirlenmiş olsaydı; herhangi bir değişken için çok sayıda varsayımsal türün takip edilmesi gerekecek, buna bağlı olarak derleyici karmaşıklaşacak ve kod hakkında daha az garanti verebileceğinden Rust bunu yapamamış olacaktı.

Döngüler ile Tekrarlama

Bazen bir kod bloğunu defalarca çalıştırmak gerekir. Rust bu amaçla döngü gövdesi içinde kalan kodun tamamını çalıştırıp hemen ardından yeniden baştan başlatan çeşitli döngüler sağlar. Döngülerle çalışabilmek için projeler dizininde donguler adında yeni bir proje başlatalım.

Rust'ta loop, while ve for olmak üzere üç çeşit döngü vardır. Bunların her birini birlikte deneyelim:

loop ile Kod Tekrarı

Bir anahtar sözcük olan loop Rust'a, ait olduğu kod bloğunu sonsuza dek ya da siz onu açıkça durdurana kadar tekrar tekrar çalıştırmasını söyler. Şimdi donguler dizinindeki src/main.rs dosyasını örnektekine benzer şekilde değiştirin:

Dosya adı: src/main.rs

fn main() {
    loop {
        println!("Tekrar!");
    }
}

Programı çalıştırdığınızda terminalinizi elle kapatana kadar Tekrar! mesajının yazdırıldığını göreceksiniz. Pekçok terminal sonsuz döngüye kapılan programların sonlandırılmasını sağlayan ctrl+c klavye kısa yolunu destekler. Demeyelim:

$ cargo run
   Compiling donguler v0.1.0 (/home/rusdili/projeler/donguler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
     Running `target/debug/donguler`
Tekrar!
Tekrar!
Tekrar!
Tekrar!
^CTekrar!

^C işareti ctrl-c tuşuna bastığınız yeri gösterir. ^C'den sonra Tekrar! yazısını görmeniz, kodun kesme sinyalini aldığında döngünün neresinde bulunduğuna bağlı olduğundan bu mesajı göremeyebilirsiniz.

Neyse ki Rust, bu tür döngülerden kod kullanarak çıkmanın bir yolunu sağlar. Programa işletilen döngünün durdurulacağı yeri, o noktaya bir break anahtar sözcüğü yerleştirerek bildirebilirsiniz. Bu yöntemi 2. Bölümdeki “Doğru Tahmin Sonrası Oyundan Çıkmak” bölümünden hatırlayor olmanız gerek.

Yine hatırlayacağınız gibi tahmin oyunu programında, döngünün o anki tekrarını durdurup bir sonraki tekrara atlayan continue anahtar kelimesini de kullanmıştık.

İçiçe döngüler söz konusu olduğunda break ve continue anahtar kelimeleri en içteki döngüye uygulanır. Dilerseniz döngü üzerinde daha sonra break ya da continue ile kullanabileceğiniz bir döngü etiketi bildirebilirsiniz. Bu durumda break ve continue anahtar kelimeleri en içteki döngüye değil etiketlenen döngüye uygulanırlar. Aşağıda iki adet içiçe geçmiş döngü örneği yer almaktadır:

fn main() {
    let mut sayaç = 0;

    'saydır: loop {
        println!("sayaç: {}", sayaç);
        let mut kalan = 10;

        loop {
            println!("Kalan: {}", kalan);
            if kalan == 9 {
                break;
            }
            if sayaç == 2 {
                break 'saydır;
            }
            kalan -= 1;
        }
        sayaç += 1;
    }
    println!("Sayaç durdu: {}", sayaç);
}

Saydır etiketine sahip olan dış döngü 0'dan 2'ye kadar sayar. Etiketsiz olan iç döngü ise 10'dan 9'a doğru geri sayım yapar. Etiketsiz olan ilk break yalnızca iç döngüden, break 'saydır; ifadesiyse dış döngüden çıkar. Bu kod aşağıdaki çıktıyı üretir:

$ cargo run
   Compiling donguler v0.1.0 (/home/rusdili/projeler/donguler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/donguler`
sayaç: 0
Kalan: 10
Kalan: 9
sayaç: 1
Kalan: 10
Kalan: 9
sayaç: 2
Kalan: 10
Sayaç durdu: 2

Döngülerden Değer Döndürmek

Döngü kullanımlarından biri de, iş parçacıklarının işlerini bitirip bitirmediğinin kontrolü gibi başarısız olması muhtemel işlemleri yeniden denemektir. Hem ayrıca işlem sonucunu bu döngünün dışında kalan kod bölümüne de aktarmanız gerekebilir. Bunu yapabilmek için döngüyü döngüyü sonlandıracak olan break ifadesinden ardından döndürülmesini istediğiniz değeri eklemek yeterlidir. Bu değer örnekte gösterildiği gibi döngüden döndürülecektir:

fn main() {
    let mut sayaç = 0;

    let sonuç = loop {
        sayaç += 1;

        if sayaç == 10 {
            break sayaç * 2;
        }
    };

    println!("Sonuç: {}", sonuç);
}

Döngüden önce sayaç adında bir değişken tanımlıyarak 0 değeriyle başlatıyoruz. Hemen ardından döngüden dönecek olan değeri depolayacağımız sonuç değişkenini tanımlıyoruz. Döngü tekrarlandıkça sayaç değerine 1 ekleneceğinden sayacın 10'a eşit olup olmadığını kontrol ediyor, değer 10 olduğunda break anahtar sözcüğüne ek olarak sayaç * 2 değerini ekliyoruz. Döngü bitiminde değeri sonuç değişkenine atayacak olan ifadeyi noktalı virgül ile bitirdiğimize dikkat edin. Nihayetinde programı sonuç değişkenine atanan 20 değerini yazdıracak şekilde tamamlayıp bitiriyoruz.

while ile Koşullu Döngüler

Programların genellikle döngü içinde bulunan koşulları değerlendirmeleri gerekir. Koşul doğru olduğu sürece çalışan döngü, koşulun yanlış olması durumunda programın break çağrısı sonucunda durdurulur. Bu tür bir davranışı if, else ve break kombinasyonlarını kullanarak uygulamak mümkündür. Eğer isterseniz bunu bir programla hemen şimdi deneyebilirsiniz. Fakat bu model o kadar yaygın biçimde kullanılmaktadır ki, Rust bunun için while döngüsü adında yerleşik bir dil yapısı sunar. Örnek 3-3'te geriye doğru 3 tur dönen ve her dönüşünde döngünün bulunduğu turu yazdıran, son olarak bir mesaj yazdırarak döngüden çıkan program için while döngüsünden yararlanıyoruz.

Dosya adı: src/main.rs

fn main() {
    let mut sayı = 3;

    while sayı != 0 {
        println!("{}!", sayı);

        sayı -= 1;
    }
    println!("Görev Tamamlandı!");
}

Örnek 3-3: Koşul doğru olduğu sürece çalışan kod için while döngüsünü kullanmak

Bu yapı, loop, if, else ve break kullanarak yazacağınız bir programda gerekli olacak çok sayıda içiçe yuvalanmayı ortadan kaldıracağı için oldukça nettir. Ve bu kod, koşul doğru olduğu sürece çalışacak aksi halde döngüden çıkacaktır.

Bir Koleksiyonu for Döngüsüyle Dolaşmak

Dizi gibi bir koleksiyonun öğeleri üzerinde yineleme yapmak için while yapısını kullanmak isteyebilirsiniz. Mesela Örnek 3-4'teki döngü a dizisindeki tüm öğeleri yazdırır:

Dosya adı: src/main.rs

fn main() {
    let a = [10, 20, 30, 40, 50];

    let mut dizin = 0;

    while dizin < 5 {
        println!("Değer: {}", a[dizin]);

        dizin += 1;
    }
}

Örnek 3-4: Bir koleksiyonun öğelerini while döngüsü kullanarak dolaşmak

Bu kod dizideki elemaları sayar. Bunu 0 dizininden başlayarak koleksiyonun sonuncu dizinine yani koşulumuz dizin < 5'in doğru olmadığı noktaya dek döngü şeklinde tekrarlayarak yapar. Bu kod çalıştırıldığında dizideki tüm öğeler yazdırırılır:

$ cargo run                                                                                                                                                                      ✔ 
   Compiling donguler v0.1.0 (/home/rusdili/projeler/donguler)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
     Running `/home/rusdili/projeler/donguler/target/debug/donguler`
Değer: 10
Değer: 20
Değer: 30
Değer: 40
Değer: 50

Beklendiği gibi dizideki beş elemanın her biri terminalde görünür. Bir noktada dizin değeri 5'e ulaşsa bile, diziden altıncı değeri alınmadan önce döngü yürütmeyi durdurur.

Ancak bu yaklaşım dizin değeri ya da test koşulunun yanlış olduğu hallerde hataya açık olup programın paniklemesine neden olur. Örneğin eğer a dizisini 4 elemandan oluşacak şekilde yeniden düzenler ve döngü koşulunu dizin < 4 şeklinde güncellemeyi unutursanız kodunuz panikleyecektir. Ayrıca bu tasarım derleyicinin, döngü boyunca her tekrarda koşulun dizi sınırlarını aşıp aşmadığını kontrol edecek ek çalışma zamanı kodları eklemesini gerektireceğinden yavaş kalacaktır.

Alternatif olarak bir koleksiyondaki her öğeyi ayrı ayrı işlemek için daha kısa ve özlü olan for döngüsünü kullanabilirsiniz. Bir for döngüsü Örnek 3-5'teki koda benzer:

Dosya adı: src/main.rs

fn main() {
    let a = [10, 20, 30, 40, 50];

    for öğe in a {
        println!("Değer: {}", öğe);
    }
}

Örnek 3-5: Bir koleksiyonun öğelerini for döngüsü kullanarak dolaşmak

Bu kodu çalıştırdığımızda Örnek 3-4'tekiyle aynı mesajları alırız. Daha da önemlisi artık kodun güvenliğini artırmış, dizi eleman sayısının ötesine geçmek ya da gereği kadar tur yapmamaktan kaynaklı bazı öğelerin işlenememesi gibi hata olasılıklarını ortadan kaldırmış olduk.

Hem ayrıca for döngüsü kullanımında dizi öğe sayısının değişmesi, Örnek 3-4'te olduğu gibi kodun yeniden güncellenmesini gerektirmez.

Kısa ve güvenle kullanılıyor olması for döngüsünün Rust'ta en yaygın kullanılan döngü yapısı olmasını sağlar. Geri sayım için while döngüsü kullanan Örnek 3-3'te olduğu gibi pekçok Rust geliştiricisi, belli sayıda tekrarlanacak kodlar için bile for döngüsünden yararlanır. Geliştiriciler bunu yaparken, belli bir başlangıç ve bitiş sayısı arasında kalan tüm sayıları sırayla üreten ve standart kitaplık tarafından sağlanan bir Range aralığı kullanırlar.

Aralığı tersine çevirebilmek içinse aşağıda gösterildiği gibi for döngüsü eşliğinde henüz görmediğimiz rev metodu kullanılır:

Dosya adı: src/main.rs

fn main() {
    for sayı in (1..4).rev() {
        println!("{}", sayı);
    }
    println!("Görev Tamamlandı!");
}

Bu kod size daha hoş görünmüyor mu?

Özet

Değişkenler, skaler ve bileşik veri türleri, işlevler, yorumlar, if ifadeleri ve döngüleri içeren oldukça büyük bir bölümü biigi sahibi olup bölümü tamamladınız. Burada tartışılan kavramları pekiştirmek amacıyla sonraki satırda önereceğimiz programları yazmayı deneyin.

  • Isı değerlerini Fahrenheit ve Celsius dereceleri arasında dönüştürün.
  • Fibonacci serisindeki n. eleman değerini hesaplayın.
  • Bir noel şarkısı olan "The Twelve Days of Christmas"ın nakaratlarını kullanarak şarkının sözlerini yazdırın.

Devam etmeye hazır olduğunuzda diğer programlama dillerinde olmayan Rust'ın mülkiyet kavramından bahsedeceğiz.

Mülkiyeti Anlamak

Mülkiyet kavramı Rust'ın bellek güvenliğini, çöp toplayıcıya ihtiyaç duymadan garanti etmesine yarayan ve dilin tamamını etkileyen eşsiz 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

Enum'lar ve Örüntü Eşleme

Bir Enum Tanımlamak

Kontrol Akışı Yapısı match

if let ile Özgün 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

UTF-8 Kodlu Metinleri Dizgilerle Saklamak

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

Hata Yönetimi

panic! ile Kurtarılamaz Hatalar

Result ile Kurtarılabilir Hatalar

panic!lemek ya da panic!lememek

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

Generic 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

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

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

Yineleyiciler ile Bir Dizi Öğeyi İşlemek

I/O Projemizi Geliştirmek

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

Cargo ve Crates.io Hakkında Daha Fazlası

Derlemeleri Sürüm Profilleriyle Özelleştirmek

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

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

Yığındaki Veriler İçin Box Kullanmak

Akıllı İşaretçilere Deref Özelliğiyle Normal Referanslarmış Gibi Davranmak

Temizlik Amaçlı Kod Çalıştırmak İçin Drop Özelliğini Kullanmak

Referans Sayılan Akıllı İşaretçi: Rc

RefCell ve İç Değişkenlik Modeli

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

Korkusuz Eşzamanlılık

İş Parçacıklarını Kullanmak

Mesajlaşma Yardımıyla Eş Zamanlı Programlama

Paylaşılan Durum Eşzamanlılığı

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 Modeli Uygulamak

Örüntüler 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üvensiz Kullanım

Gelişmiş Özellikler

Gelişmiş Türler

Gelişmiş Kapamalar ve İşlevler

Makrolar

Son Proje: Çok İş Parçacıklı Bir Web Sunucusu Oluşturmak

Tek İş Parçacıklı Bir Web Sunucusu Oluşturmak

Tek İş Parçacıklı Sunucumuzu Çok İş Parçacıklı Bir Sunucuya Dönüştürmek

Sorunsuzca Kapatmak ve Temizlik

Ekler

A - Anahtar Kelimeler

B - İşleçler ve Semboller

C - Türetilebilir Özellikler

D - Faydalı Geliştirme Araçları

E - Sürümler

F - Kitabın Çevirileri

G - Rust Nasıl “Nightly Rust” Yapılır?