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, enum
lar (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 enum
lardan 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.
Ferris | Anlamı |
---|---|
Bu kod derlenmez! | |
Bu kod panik üretir! | |
Bu kod bloğu güvenli olmayan kod içerir. | |
Bu kod beklenen davranışı üretmiyor. |
Çoğu durumda, hangi kod sürümünün çalışması gerektiğine dair sizi yönlendireceğiz.
Kaynak Kodu
Bu kitabın oluşmasını sağlayan kaynak kodlara [GitHub][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!"); }
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]
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);
}
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
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);
}
Ö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ğinrand
sandığındaki bulunan diğer işlevler hakkında bilgilenmek istiyorsanız,cargo doc --open
komutunu çalıştırarak, sol kenar çubuğunda yer alanrand
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!"),
}
}
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;
}
}
}
}
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;
}
}
}
}
Ö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-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
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.
Sayısal Değişmez | Örnek |
---|---|
Ondalık | 98_222 |
Onaltılık | 0xff |
Sekizlik | 0o77 |
İkilik | 0b1111_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ınu8
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
gibiwrapping_*
metodlarıyla sarmalayın.- Taşmanın gerçekleşebileceği durumları
checked_*
metodlarıyla denetleyipNone
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, ikinci
adı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; }
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ı); }
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ı!"); }
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; } }
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); } }
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.