Tahmin Oyunu Programlama
Birlikte uygulamalı bir proje üzerinde çalışarak Rust’a giriş yapalım! Bu bölüm,
bazı yaygın Rust kavramlarını gerçek bir programda nasıl kullanacağınızı göstererek
size tanıtır. let, match, metotlar, ilişkili fonksiyonlar (associated functions),
harici crate’ler ve daha fazlası hakkında bilgi edineceksiniz! İlerleyen bölümlerde bu
fikirleri daha ayrıntılı olarak inceleyeceğiz. Bu bölümde sadece temel bilgilerin pratiğini
yapacaksınız.
Klasik bir başlangıç programlama problemini uygulayacağız: bir tahmin oyunu. Şöyle çalışıyor: Program 1 ile 100 arasında rastgele bir tam sayı üretecek. Daha sonra oyuncudan bir tahmin girmesini isteyecek. Bir tahmin girildikten sonra, program tahminin çok düşük veya çok yüksek olduğunu belirtecek. Tahmin doğruysa, oyun bir tebrik mesajı yazdıracak ve kapanacak.
Yeni Bir Proje Kurulumu
Yeni bir proje kurmak için, Bölüm 1’de oluşturduğunuz projects dizinine gidin ve Cargo’yu kullanarak yeni bir proje oluşturun, örneğin:
$ cargo new tahmin_oyunu
$ cd tahmin_oyunu
İlk komut olan cargo new, projenin adını (tahmin_oyunu) ilk argüman olarak alır.
İkinci komut ise dizini yeni projenin dizinine değiştirir.
Oluşturulan Cargo.toml dosyasına bakın:
Dosya adı: Cargo.toml
[package]
name = "tahmin_oyunu"
version = "0.1.0"
edition = "2024"
[dependencies]
Bölüm 1’de gördüğünüz gibi, cargo new sizin için bir “Merhaba, dünya!” programı üretir.
src/main.rs dosyasına göz atın:
Dosya adı: src/main.rs
fn main() {
println!("Merhaba, dünya!");
}
Şimdi bu “Merhaba, dünya!” programını derleyelim ve aynı adımda cargo run komutunu
kullanarak çalıştıralım:
$ cargo run
Compiling tahmin_oyunu v0.1.0 ($PROJE/listings/ch02-guessing-game-tutorial/no-listing-01-cargo-new)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
Running `target/debug/tahmin_oyunu`
Merhaba, dünya!
run komutu, bu oyunda da yapacağımız gibi, bir projede hızlıca ardışık geliştirmeler
yapmanız ve bir sonrakine geçmeden önce her iterasyonu hızlıca test etmeniz gerektiğinde çok işe yarar.
src/main.rs dosyasını tekrar açın. Tüm kodu bu dosyaya yazacaksınız.
Bir Tahmini İşlemek
Tahmin oyunu programının ilk bölümü kullanıcıdan girdi isteyecek, bu girdiyi işleyecek ve girdinin beklenen biçimde olup olmadığını kontrol edecektir. Başlamak için, oyuncunun bir tahmin girmesine izin vereceğiz. Liste 2-1’deki kodu src/main.rs dosyasına girin.
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Bu kod pek çok bilgi içeriyor, bu yüzden satır satır üzerinden geçelim.
Kullanıcı girdisini almak ve ardından sonucu çıktı olarak yazdırmak için
io (girdi/çıktı) kütüphanesini kapsama dahil etmemiz gerekir.
io kütüphanesi, std olarak bilinen standart kütüphaneden gelir:
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Varsayılan olarak Rust, standart kütüphanede tanımlanmış ve her programın kapsamına dahil ettiği bir dizi öğeye sahiptir. Bu diziye prelude (başlangıç) denir ve içindeki her şeyi standart kütüphane dokümantasyonunda görebilirsiniz.
Eğer kullanmak istediğiniz bir tür prelude içinde değilse, o türü bir use
ifadesi ile açıkça kapsama dahil etmeniz gerekir. std::io kütüphanesini
kullanmak, kullanıcı girdisini kabul etme yeteneği de dahil olmak üzere size
bir dizi yararlı özellik sağlar.
Bölüm 1’de gördüğünüz gibi, main fonksiyonu programa giriş noktasıdır:
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
fn sözdizimi (syntax) yeni bir fonksiyon tanımlar; () parantezleri
parametre olmadığını gösterir; ve { süslü parantezi fonksiyonun gövdesini başlatır.
Yine Bölüm 1’de öğrendiğiniz gibi, println! ekrana bir metin (string) yazdıran
bir makrodur:
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Bu kod, oyunun ne olduğunu belirten bir metin yazdırıyor ve kullanıcıdan girdi talep ediyor.
Değerleri Değişkenlerle Saklamak
Sırada, kullanıcı girdisini saklamak için aşağıdaki gibi bir değişken (variable) oluşturacağız:
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Şimdi program ilginçleşmeye başlıyor! Bu küçük satırda pek çok şey oluyor. Değişkeni
oluşturmak için let ifadesini kullanıyoruz. İşte başka bir örnek:
let elmalar = 5;
Bu satır elmalar adında yeni bir değişken oluşturur ve onu 5 değerine bağlar (bind).
Rust’ta değişkenler varsayılan olarak değiştirilemezdir, yani değişkene
bir değer verdiğimizde bu değer değişmez. Bu kavramı Bölüm 3’teki
“Değişkenler ve Değiştirilebilirlik” kısmında
ayrıntılı olarak tartışacağız. Bir değişkeni değiştirilebilir yapmak için
değişken adından önce mut ekleriz:
let elmalar = 5; // değiştirilemez
let mut muzlar = 5; // değiştirilebilir
Not:
//sözdizimi, satırın sonuna kadar devam eden bir yorum (comment) başlatır. Rust, yorumlardaki her şeyi görmezden gelir. Yorumları Bölüm 3’te daha ayrıntılı olarak tartışacağız.
Tahmin oyunu programına dönecek olursak, artık let mut tahmin’in tahmin adında
değiştirilebilir bir değişken tanıtacağını biliyorsunuz. Eşittir işareti (=),
Rust’a değişkeni şu an bir şeye bağlamak istediğimizi söyler. Eşittir işaretinin
sağında, tahmin değişkeninin bağlandığı değer bulunur; bu değer, yeni bir String
örneği döndüren bir fonksiyon olan String::new’i çağırmanın sonucudur.
String, standart kütüphane tarafından sağlanan; genişleyebilir,
UTF-8 kodlu bir metin türüdür.
::new satırındaki :: sözdizimi, new’in String türünün ilişkili (associated) bir
fonksiyonu olduğunu gösterir. Bir ilişkili fonksiyon, bir tür (burada String)
üzerinde uygulanan (implemented) fonksiyondur. Bu new fonksiyonu yeni ve boş bir
metin (string) oluşturur. Birçok türde bir new fonksiyonu bulacaksınız, çünkü bu,
yeni bir değer üreten bir fonksiyon için yaygın olarak kullanılan bir isimdir.
Özetle, let mut tahmin = String::new(); satırı, şu anda yeni ve boş bir String
örneğine bağlı olan, değiştirilebilir bir değişken oluşturmuştur. Vay canına!
Kullanıcı Girdisini Almak
Programın ilk satırında standart kütüphaneden girdi/çıktı işlevselliğini use std::io; ile
dahil ettiğimizi hatırlayın. Şimdi io modülünden stdin fonksiyonunu çağıracağız,
bu da kullanıcı girdisini ele almamızı sağlayacak:
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Eğer io modülünü programın başında use std::io; ile içe aktarmasaydık (import),
yine de bu fonksiyon çağrısını std::io::stdin olarak yazarak kullanabilirdik.
stdin fonksiyonu, terminaliniz için standart girdiye erişimi temsil eden
bir tür olan std::io::Stdin örneğini döndürür.
Ardından gelen .read_line(&mut tahmin) satırı, kullanıcıdan girdi almak için
standart girdi arayüzünde read_line metotunu çağırır.
Ayrıca, kullanıcı girdisinin hangi metne (string) kaydedileceğini söylemek için &mut tahmin
ifadesini read_line’a argüman olarak veriyoruz. read_line’ın tüm işi, kullanıcının
standart girdiye yazdıklarını almak ve bunu bir metne eklemektir (içeriğinin üzerine yazmadan).
Bu nedenle o metni bir argüman olarak aktarıyoruz. Metin argümanı değiştirilebilir olmalıdır,
böylece metot metnin içeriğini değiştirebilir.
& işareti, bu argümanın bir referans olduğunu gösterir. Bu, veriyi belleğe birden fazla
kez kopyalamanıza gerek kalmadan kodunuzun birden fazla parçasının aynı veriye
erişmesine izin veren bir yoldur. Referanslar karmaşık bir özelliktir ve Rust’ın en büyük
avantajlarından biri referansları kullanmanın ne kadar güvenli ve kolay olmasıdır. Bu programı
bitirmek için bu detayların çoğunu bilmenize gerek yoktur. Şimdilik bilmeniz gereken tek şey,
tıpkı değişkenler gibi, referansların da varsayılan olarak değiştirilemez olduğudur.
Bu nedenle onu değiştirilebilir yapmak için &tahmin yerine &mut tahmin
yazmanız gerekir. (Bölüm 4, referansları daha kapsamlı bir şekilde açıklayacaktır.)
Potansiyel Bir Hatayı Result ile Ele Almak
Hala aynı kod satırı üzerinde çalışıyoruz. Şimdi kodun üçüncü satırını tartışıyoruz, ancak bunun hala tek bir mantıksal kod satırının bir parçası olduğunu unutmayın. Sonraki kısım bu metottur:
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Bu kodu şu şekilde de yazabilirdik:
io::stdin().read_line(&mut tahmin).expect("Satır okunamadı");
Ancak tek bir uzun satırı okumak zordur, bu yüzden onu bölmek en iyisidir.
.method_adi() sözdizimi ile bir metot çağırdığınızda uzun satırları bölmeye yardımcı
olması için yeni bir satır (newline) ve diğer boşlukları (whitespace) kullanmak
genellikle akıllıcadır. Şimdi bu satırın ne işe yaradığını tartışalım.
Daha önce bahsedildiği gibi, read_line kullanıcının girdiği her şeyi
ona aktardığımız metne (string) koyar, ancak aynı zamanda bir Result değeri döndürür.
Result, çoğunlukla bir enum olarak adlandırılan
bir enumeration’dır. Bu, birden fazla olası durumdan
birinde olabilen bir türdür. Her olası duruma varyant (variant) diyoruz.
Bölüm 6 enum’ları daha ayrıntılı olarak ele alacaktır.
Bu Result türlerinin amacı, hata ayıklama bilgilerini kodlamaktır.
Result’ın varyantları Ok ve Err’dir. Ok varyantı işlemin başarılı olduğunu
gösterir ve başarıyla üretilen değeri içerir. Err varyantı işlemin başarısız olduğu
anlamına gelir ve işlemin nasıl veya neden başarısız olduğuna dair bilgiler içerir.
Result türündeki değerler, tıpkı herhangi bir türdeki değerler gibi, üzerlerinde
tanımlanmış metotlara sahiptir. Bir Result örneğinin çağırabileceğiniz bir
expect metodu vardır. Eğer bu Result örneği bir
Err değeriyse, expect programın çökmesine (crash) neden olur ve
expect’e argüman olarak verdiğiniz mesajı görüntüler. Eğer read_line
metodu bir Err döndürdüyse, bu muhtemelen altta yatan işletim sisteminden kaynaklanan
bir hatanın sonucu olacaktır. Eğer bu Result örneği bir Ok değeriyse,
expect, Ok’un barındırdığı dönüş değerini alacak ve kullanabilmeniz
için sadece bu değeri size döndürecektir. Bu durumda, bu değer kullanıcının
girdisindeki bayt (byte) sayısıdır.
Eğer expect’i çağırmazsanız, program derlenecektir ancak bir uyarı (warning)
alacaksınız:
$ cargo build
Compiling tahmin_oyunu v0.1.0 ($PROJE/listings/ch02-guessing-game-tutorial/no-listing-02-without-expect)
warning: unused `Result` that must be used
--> src/main.rs:10:5
|
10 | io::stdin().read_line(&mut tahmin);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this `Result` may be an `Err` variant, which should be handled
= note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
|
10 | let _ = io::stdin().read_line(&mut tahmin);
| +++++++
warning: `tahmin_oyunu` (bin "tahmin_oyunu") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
Rust, read_line’dan dönen Result değerini kullanmadığınız konusunda sizi
uyararak programın olası bir hatayı ele almadığını belirtir.
Uyarıyı bastırmanın doğru yolu, aslında hata yönetimi (error-handling)
kodunu yazmaktır; ancak bizim durumumuzda bir sorun oluştuğunda programın
çökmesini istiyoruz, bu nedenle expect kullanabiliriz. Hatalardan kurtulmayı
(recovering from errors) Bölüm 9’da öğreneceksiniz.
println! Yer Tutucuları (Placeholders) ile Değerleri Yazdırmak
Kapanış süslü parantezini saymazsak, şu ana kadarki kodda tartışılacak sadece bir satır daha kaldı:
use std::io;
fn main() {
println!("Sayıyı tahmin et!");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Bu satır, artık kullanıcının girdisini içeren metni yazdırır. {} şeklindeki
süslü parantez takımı bir yer tutucudur (placeholder): {}’yi, bir değeri
yerinde tutan küçük yengeç kıskaçları gibi düşünün. Bir değişkenin değerini
yazdırırken, değişken adı süslü parantezlerin içine gidebilir. Bir ifadenin
değerlendirilmesinin sonucunu yazdırırken, format dizisine (format string) boş süslü
parantezler yerleştirin, ardından format dizisinden sonra virgülle ayrılmış bir
ifadeler listesi (list of expressions) ekleyin; bu ifadeler her bir boş süslü
parantez yer tutucusunda aynı sırayla yazdırılacaktır. Bir değişkeni ve bir ifadenin
sonucunu tek bir println! çağrısıyla yazdırmak şu şekilde görünecektir:
#![allow(unused)]
fn main() {
let x = 5;
let y = 10;
println!("x = {x} ve y + 2 = {}", y + 2);
}
Bu kod x = 5 ve y + 2 = 12 yazdıracaktır.
İlk Kısmı Test Etmek
Tahmin oyununun ilk bölümünü test edelim. cargo run komutunu kullanarak çalıştırın:
$ cargo run
Compiling tahmin_oyunu v0.1.0 (file:///projects/tahmin_oyunu)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.44s
Running `target/debug/tahmin_oyunu`
Sayıyı tahmin et!
Lütfen tahmininizi girin.
6
Tahmininiz: 6
Bu noktada, oyunun ilk kısmı tamamlandı: Klavyeden girdi alıyoruz ve ardından onu yazdırıyoruz.
Gizli Bir Sayı Üretmek
Sırada, kullanıcının tahmin etmeye çalışacağı gizli bir sayı üretmemiz gerekiyor.
Oyunun birden fazla kez oynanmasının eğlenceli olması için gizli sayı her seferinde
farklı olmalıdır. Oyunun çok zor olmaması için 1 ile 100 arasında rastgele bir
sayı kullanacağız. Rust henüz standart kütüphanesinde rastgele sayı işlevselliği
içermiyor. Ancak Rust ekibi, söz konusu işlevselliğe sahip bir rand crate’i
sağlıyor.
Bir Crate ile İşlevselliği Artırmak
Bir crate’in, Rust kaynak kodu dosyalarından oluşan bir koleksiyon olduğunu hatırlayın.
Geliştirdiğimiz proje çalıştırılabilir bir crate’tir. rand crate’i ise,
başka programlarda kullanılması amaçlanan ve kendi başına çalıştırılamayan kodlar içeren bir
kütüphane (library) crate’idir.
Harici crate’lerin koordinasyonu, Cargo’nun gerçekten parladığı yerdir. rand kullanan
bir kod yazabilmemiz için önce Cargo.toml dosyasını değiştirip rand crate’ini
bir bağımlılık (dependency) olarak eklememiz gerekir. Şimdi o dosyayı açın ve Cargo’nun
sizin için oluşturduğu [dependencies] bölüm başlığının hemen altına şu satırı ekleyin.
rand’i bu sürüm numarasıyla birlikte aynen burada verdiğimiz gibi belirttiğinizden emin
olun, aksi takdirde bu öğreticideki kod örnekleri çalışmayabilir:
Dosya adı: Cargo.toml
[dependencies]
rand = "0.8.5"
Cargo.toml dosyasında, bir başlıktan sonra gelen her şey, başka bir bölüm
başlayana kadar devam eden o bölümün parçasıdır. [dependencies] bölümünde
Cargo’ya, projenizin hangi harici crate’lere bağımlı olduğunu ve bu crate’lerin
hangi sürümlerini gerektirdiğinizi söylersiniz. Bu örnekte, rand crate’ini semantik (anlamsal)
sürüm belirteci 0.8.5 ile belirtiyoruz. Cargo, sürüm numaralarını yazmak için bir
standart olan Semantik Sürümlemeyi (bazen SemVer olarak
adlandırılır) anlar. 0.8.5 belirteci aslında ^0.8.5 için bir kısaltmadır ve
bu da en az 0.8.5 olan ancak 0.9.0’ın altında kalan tüm sürümleri kapsadığı anlamına gelir.
Cargo bu sürümlerin 0.8.5 sürümüyle uyumlu genel API’lere sahip olduğunu kabul eder ve bu spesifikasyon, bu bölümdeki kodla hâlâ derlenebilecek en son yama sürümünü (patch release) almanızı garanti eder. Sürümü 0.9.0 veya daha büyük olanların ise aşağıdaki örneklerin kullandığıyla aynı API’ye sahip olması garanti edilmez.
Şimdi koddaki hiçbir şeyi değiştirmeden Liste 2-2’de gösterildiği gibi projeyi derleyelim.
$ cargo build
Updating crates.io index
Locking 15 packages to latest Rust 1.85.0 compatible versions
Adding rand v0.8.5 (available: v0.9.0)
Compiling proc-macro2 v1.0.93
Compiling unicode-ident v1.0.17
Compiling libc v0.2.170
Compiling cfg-if v1.0.0
Compiling byteorder v1.5.0
Compiling getrandom v0.2.15
Compiling rand_core v0.6.4
Compiling quote v1.0.38
Compiling syn v2.0.98
Compiling zerocopy-derive v0.7.35
Compiling zerocopy v0.7.35
Compiling ppv-lite86 v0.2.20
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
Compiling tahmin_oyunu v0.1.0 (file:///projects/tahmin_oyunu)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s
rand crate’ini bağımlılık olarak ekledikten sonra cargo build çalıştırmanın çıktısıFarklı sürüm numaraları görebilirsiniz (ancak SemVer sayesinde hepsi kodla uyumlu olacaktır!) ve farklı satırlar (işletim sistemine bağlı olarak) olabilir, ve satırlar farklı bir sırada olabilir.
Harici bir bağımlılığı dahil ettiğimizde, Cargo o bağımlılığın ihtiyaç duyduğu her şeyin en son sürümlerini, Crates.io’daki verilerin bir kopyası olan kayıt defterinden (registry) getirir (fetch). Crates.io, Rust ekosistemindeki insanların başkalarının kullanması için açık kaynaklı Rust projelerini yayınladıkları yerdir.
Cargo, kayıt defterini güncelledikten sonra [dependencies] bölümünü kontrol
eder ve listelenen fakat henüz indirilmemiş crate’leri indirir. Bu örnekte
sadece rand’i bir bağımlılık olarak listelemiş olsak da, Cargo ayrıca rand’in
çalışması için bağımlı olduğu diğer crate’leri de alır. Crate’leri indirdikten sonra,
Rust bunları derler ve ardından bağımlılıklar mevcut olacak şekilde projeyi
derler.
Eğer hiçbir değişiklik yapmadan cargo build komutunu hemen tekrar çalıştırırsanız,
Finished satırı haricinde herhangi bir çıktı almazsınız. Cargo, bağımlılıkları daha
önceden indirip derlediğini ve sizin Cargo.toml dosyanızda bunlar hakkında hiçbir şeyi
değiştirmediğinizi bilir. Ayrıca kodunuz hakkında da hiçbir şeyi değiştirmediğinizi bilir,
bu yüzden onu da yeniden derlemez. Yapacak bir şeyi olmadığı için basitçe çıkış yapar.
Eğer src/main.rs dosyasını açıp önemsiz bir değişiklik yaparsanız ve ardından kaydedip tekrar derlerseniz, yalnızca iki satır çıktı görürsünüz:
$ cargo build
Compiling tahmin_oyunu v0.1.0 (file:///projects/tahmin_oyunu)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
Bu satırlar, Cargo’nun derlemeyi yalnızca src/main.rs dosyanızdaki ufak değişikliğinizle güncellediğini gösterir. Bağımlılıklarınız değişmedi, bu yüzden Cargo, bunlar için zaten indirip derlediği şeyleri yeniden kullanabileceğini biliyor.
Tekrarlanabilir Derlemeleri Sağlamak (Reproducible Builds)
Cargo, siz veya başka herhangi bir kişi kodunuzu her derlediğinde aynı artefaktı
(artifact) yeniden derleyebilmenizi sağlayan bir mekanizmaya sahiptir: Cargo, siz
aksi bir şey belirtene kadar yalnızca belirttiğiniz bağımlılık sürümlerini
kullanacaktır. Örneğin, gelecek hafta rand crate’inin 0.8.6 sürümünün çıktığını,
ve bu sürümün önemli bir hata düzeltmesi içerdiğini, ancak kodunuzu bozacak bir
gerileme (regression) de barındırdığını varsayalım. Bunun üstesinden gelmek için,
Rust ilk cargo build komutunu çalıştırdığınızda Cargo.lock dosyasını oluşturur,
ve biz şu an tahmin_oyunu dizinimizde buna sahibiz.
Bir projeyi ilk kez derlediğinizde, Cargo kriterlere uyan bağımlılıkların tüm sürümlerini belirler (figures out) ve daha sonra bunları Cargo.lock dosyasına yazar. Projenizi gelecekte derlediğinizde Cargo, Cargo.lock dosyasının mevcut olduğunu görecek ve sürümleri yeniden belirleme işini yapmaktansa orada belirtilen sürümleri kullanacaktır. Bu, tekrarlanabilir bir derlemeye otomatik olarak sahip olmanızı sağlar. Başka bir deyişle, siz açıkça yükseltme (upgrade) yapana kadar, Cargo.lock dosyası sayesinde projeniz 0.8.5 sürümünde kalacaktır. Cargo.lock dosyası, tekrarlanabilir derlemeler için önemli olduğundan, genellikle projenizdeki geri kalan kodlarla birlikte sürüm kontrolüne (source control) dahil edilir.
Yeni Bir Sürüm Almak İçin Bir Crate’i Güncellemek
Bir crate’i güncellemek istediğinizde, Cargo update komutunu sağlar;
bu komut Cargo.lock dosyasını yok sayacak ve Cargo.toml dosyasındaki
spesifikasyonlarınıza uyan en son sürümleri belirleyecektir. Ardından Cargo,
bu sürümleri Cargo.lock dosyasına yazacaktır. Aksi takdirde, varsayılan olarak Cargo
yalnızca 0.8.5’ten büyük ve 0.9.0’dan küçük sürümleri arayacaktır. Eğer rand crate’i
iki yeni sürüm olan 0.8.6 ve 0.999.0’ı yayınlamışsa, cargo update çalıştırdığınızda
şunu görürdünüz:
$ cargo update
Updating crates.io index
Locking 1 package to latest Rust 1.85.0 compatible version
Updating rand v0.8.5 -> v0.8.6 (available: v0.999.0)
Cargo, 0.999.0 sürümünü yok sayar. Bu noktada Cargo.lock dosyanızda artık kullandığınız
rand crate sürümünün 0.8.6 olduğunu belirten bir değişiklik fark edersiniz. rand 0.999.0 sürümünü
veya 0.999.x serisindeki herhangi bir sürümü kullanmak için, bunun yerine Cargo.toml dosyanızı
şu şekilde güncellemeniz gerekir (bu değişikliği yapmayın çünkü aşağıdaki örnekler
rand 0.8 kullandığınızı varsayıyor):
[dependencies]
rand = "0.999.0"
cargo build’i bir sonraki çalıştırışınızda, Cargo, mevcut crate’lerin kayıt
defterini güncelleyecek ve belirttiğiniz yeni sürüme göre rand gereksinimlerinizi
yeniden değerlendirecektir.
Cargo ve ekosistemi hakkında söylenecek çok daha fazla şey var ve bunları Bölüm 14’te tartışacağız; ancak şimdilik bilmeniz gerekenler bunlar. Cargo, kütüphaneleri yeniden kullanmayı çok kolaylaştırdığı için, Rust geliştiricileri (Rustaceans) bir dizi paketten birleştirilmiş daha küçük projeler yazabilirler.
Rastgele Bir Sayı Üretmek
Tahmin edilecek sayıyı üretmek için rand kullanmaya başlayalım. Bir sonraki
adım, Liste 2-3’te gösterildiği gibi src/main.rs dosyasını güncellemektir.
use std::io;
use rand::Rng;
fn main() {
println!("Sayıyı tahmin et!");
let gizli_sayi = rand::thread_rng().gen_range(1..=100);
println!("Gizli sayı: {gizli_sayi}");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
}
Öncelikle, use rand::Rng; satırını ekliyoruz. Rng trait’i (özelliği),
rastgele sayı üreteçlerinin (generators) uyguladığı metotları
tanımlar ve bu metotları kullanabilmemiz için bu trait kapsamda olmalıdır.
Bölüm 10, trait’leri detaylı bir şekilde ele alacaktır.
Sonrasında ortaya iki satır ekliyoruz. İlk satırda, kullanacağımız belirli
rastgele sayı üretecini bize veren rand::thread_rng fonksiyonunu çağırıyoruz:
Bu üreteç, yürütmenin mevcut iş parçacığına özeldir (local)
ve işletim sistemi tarafından tohumlanır (seeded). Sonra da rastgele sayı
üreteci üzerinde gen_range metodunu çağırıyoruz. Bu metot, use rand::Rng;
ifadesi ile kapsama dahil ettiğimiz Rng trait’i tarafından tanımlanmıştır.
gen_range metodu argüman olarak bir aralık ifadesi (range expression) alır
ve o aralıkta rastgele bir sayı üretir. Burada kullandığımız aralık ifadesi türü
baslangic..=bitis (start..=end) formundadır ve alt ile üst sınırları dahil
eder; bu nedenle 1 ile 100 arasında bir sayı talep etmek için 1..=100 belirtmemiz gerekir.
Not: Bir crate’ten hangi trait’leri kullanacağınızı ve hangi metotları ile fonksiyonları çağıracağınızı hemen bilemezsiniz; bu yüzden her crate’in kullanımı ile ilgili talimatları içeren dokümantasyonları vardır. Cargo’nun başka bir şık özelliği de,
cargo doc --openkomutunu çalıştırmanın tüm bağımlılıklarınız tarafından sağlanan belgeleri yerel olarak oluşturması (build) ve bunları tarayıcınızda açmasıdır. Örneğin,randcrate’indeki diğer işlevlerle ilgileniyorsanız,cargo doc --opençalıştırın ve soldaki kenar çubuğundan (sidebar)rand’a tıklayın.
İkinci yeni satır gizli sayıyı yazdırır. Bu, programı geliştirirken onu test edebilmek için kullanışlıdır, ancak nihai sürümden bunu sileceğiz. Program, başlar başlamaz cevabı yazdırıyorsa pek de bir oyun sayılmaz!
Programı birkaç kez çalıştırmayı deneyin:
$ cargo run
Compiling tahmin_oyunu v0.1.0 (file:///projects/tahmin_oyunu)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/tahmin_oyunu`
Sayıyı tahmin et!
Gizli sayı: 7
Lütfen tahmininizi girin.
4
Tahmininiz: 4
$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/tahmin_oyunu`
Sayıyı tahmin et!
Gizli sayı: 83
Lütfen tahmininizi girin.
5
Tahmininiz: 5
Farklı rastgele sayılar almalısınız ve hepsi de 1 ile 100 arasında sayılar olmalıdır. Harika iş çıkardınız!
Tahmini Gizli Sayıyla Karşılaştırmak
Artık kullanıcı girdisine ve rastgele bir sayıya sahip olduğumuza göre, onları karşılaştırabiliriz. Bu adım Liste 2-4’te gösterilmektedir. Not: Bu kod daha sonra açıklayacağımız nedenlerden ötürü henüz derlenmeyecektir.
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
// --snip--
println!("Sayıyı tahmin et!");
let gizli_sayi = rand::thread_rng().gen_range(1..=100);
println!("Gizli sayı: {gizli_sayi}");
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
println!("Tahmininiz: {tahmin}");
match tahmin.cmp(&gizli_sayi) {
Ordering::Less => println!("Çok küçük!"),
Ordering::Greater => println!("Çok büyük!"),
Ordering::Equal => println!("Kazandınız!"),
}
}
Öncelikle, standart kütüphaneden std::cmp::Ordering adlı bir türü kapsama
dahil eden başka bir use ifadesi ekliyoruz. Ordering türü başka bir enum’dur
ve Less (Daha Küçük), Greater (Daha Büyük) ve Equal (Eşit) varyantlarına
sahiptir. Bunlar, iki değeri karşılaştırdığınızda mümkün olan üç sonuçtur.
Sonra en alta Ordering türünü kullanan beş yeni satır ekliyoruz. cmp
metodu iki değeri karşılaştırır ve karşılaştırılabilen herhangi bir şey üzerinde
çağrılabilir. Ne ile karşılaştırma yapmak istiyorsanız onun bir referansını
alır: Burada tahmin’i gizli_sayi ile karşılaştırıyor. Sonrasında, use
ifadesiyle kapsama dahil ettiğimiz Ordering enum’ının bir varyantını döndürür.
cmp çağrısından, tahmin ve gizli_sayi değerleriyle hangi Ordering
varyantının döndüğüne bağlı olarak bundan sonra ne yapacağımıza karar
vermek için bir match ifadesi kullanıyoruz.
Bir match ifadesi kollardan (arms) oluşur. Bir kol (arm), eşleştirilecek
bir desenden (pattern) ve match’e verilen değer o kolun desenine uyduğunda
çalıştırılması gereken koddan oluşur. Rust, match’e verilen değeri alır
ve sırayla her bir kolun deseni boyunca bakar. Desenler (patterns) ve
match yapısı çok güçlü Rust özellikleridir: Kodunuzun karşılaşabileceği
çeşitli durumları ifade etmenizi ve hepsini ele aldığınızdan emin olmanızı sağlarlar.
Bu özellikler sırasıyla Bölüm 6 ve Bölüm 19’da ayrıntılı olarak ele alınacaktır.
Burada kullandığımız match ifadesi ile bir örnek üzerinden gidelim.
Diyelim ki kullanıcı 50 tahmininde bulundu ve bu kez rastgele üretilen gizli sayı 38.
Kod 50’yi 38 ile karşılaştırdığında, cmp metodu Ordering::Greater (Daha Büyük)
döndürecektir, çünkü 50, 38’den büyüktür. match ifadesi Ordering::Greater
değerini alır ve her bir kolun desenini (pattern) kontrol etmeye başlar.
İlk kolun deseni olan Ordering::Less’e bakar ve Ordering::Greater
değerinin Ordering::Less (Daha Küçük) ile eşleşmediğini görür; bu yüzden
o koldaki kodu yok sayar ve bir sonraki kola geçer. Sonraki kolun
deseni Ordering::Greater’dır, ki bu da Ordering::Greater ile eşleşir!
O koldaki ilişkili kod çalıştırılacak ve ekrana Çok büyük! yazdıracaktır.
match ifadesi ilk başarılı eşleşmeden sonra sona erer, bu senaryoda
artık son kola bakmayacaktır.
Fakat Liste 2-4’teki kod henüz derlenmeyecektir. Deneyelim:
$ cargo build
Compiling libc v0.2.86
Compiling getrandom v0.2.2
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.10
Compiling rand_core v0.6.2
Compiling rand_chacha v0.3.0
Compiling rand v0.8.5
Compiling tahmin_oyunu v0.1.0 ($PROJE/listings/ch02-guessing-game-tutorial/listing-02-04)
error[E0308]: mismatched types
--> src/main.rs:27:22
|
27 | match tahmin.cmp(&gizli_sayi) {
| --- ^^^^^^^^^^^ expected `&String`, found `&{integer}`
| |
| arguments to this method are incorrect
|
= note: expected reference `&String`
found reference `&{integer}`
note: method defined here
--> $HOME/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cmp.rs:991:8
|
991 | fn cmp(&self, other: &Self) -> Ordering;
| ^^^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `tahmin_oyunu` (bin "tahmin_oyunu") due to 1 previous error
Hatanın özü uyumsuz türler (mismatched types) olduğunu belirtiyor.
Rust güçlü, statik bir tür sistemine (static type system) sahiptir. Ancak,
aynı zamanda tür çıkarımı (type inference) da vardır. let mut tahmin = String::new()
yazdığımızda, Rust tahmin’in bir String olması gerektiği çıkarımını
yapabildi ve bizim türü yazmamıza gerek bırakmadı. Öte yandan, gizli_sayi
bir sayı türüdür. Rust’ın 1 ile 100 arasında değer alabilen birkaç
sayı türü vardır: 32 bitlik bir sayı olan i32; işaretsiz (unsigned) 32 bitlik
bir sayı olan u32; 64 bitlik bir sayı olan i64; ve diğerleri. Aksi
belirtilmediği sürece Rust varsayılan olarak i32 kullanır. Eğer Rust’ın
başka bir sayısal tür çıkarımı yapmasına neden olacak tür bilgisini
başka bir yere eklemediyseniz, gizli_sayi’nın türü budur. Hatanın nedeni,
Rust’ın bir metin türü (string) ile bir sayı türünü karşılaştıramamasıdır.
Nihayetinde, programın girdi olarak okuduğu String’i bir sayı türüne
dönüştürmek istiyoruz, böylece onu gizli sayıyla sayısal olarak
karşılaştırabileceğiz. Bunu main fonksiyonunun gövdesine
şu satırı ekleyerek yapıyoruz:
Dosya adı: src/main.rs
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("Sayıyı tahmin et!");
let gizli_sayi = rand::thread_rng().gen_range(1..=100);
println!("Gizli sayı: {gizli_sayi}");
println!("Lütfen tahmininizi girin.");
// --snip--
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı yazın!");
println!("Tahmininiz: {tahmin}");
match tahmin.cmp(&gizli_sayi) {
Ordering::Less => println!("Çok küçük!"),
Ordering::Greater => println!("Çok büyük!"),
Ordering::Equal => println!("Kazandınız!"),
}
}
O satır şudur:
let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı yazın!");
tahmin adında bir değişken oluşturuyoruz. Ama bir dakika, programda zaten
tahmin adında bir değişken yok muydu? Var, ancak Rust yararlı bir şekilde
tahmin’in önceki değerini yenisiyle gölgelendirmemize (shadow) olanak tanır.
Gölgelendirme, bizi örneğin tahmin_str ve tahmin gibi
iki farklı değişken oluşturmaya zorlamak yerine tahmin değişken adını
yeniden kullanmamıza olanak tanır. Bunu Bölüm 3’te
daha ayrıntılı ele alacağız, ancak şimdilik bu özelliğin genellikle
bir değeri bir türden diğerine dönüştürmek istediğinizde kullanıldığını bilin.
Bu yeni değişkeni tahmin.trim().parse() ifadesine bağlıyoruz.
İfadedeki tahmin, girdiyi metin (string) olarak barındıran
orijinal tahmin değişkenine işaret eder. Bir String örneği üzerindeki
trim metodu, metnin başındaki ve sonundaki tüm boşlukları ortadan kaldıracaktır.
Metni (string) yalnızca sayısal veri içerebilen bir u32’ye dönüştürmeden önce
bunu yapmamız gerekir. Kullanıcının read_line’ı tamamlamak ve tahminini
girmek için enter (giriş) tuşuna basması gerekir; bu da metne
bir yeni satır (newline) karakteri ekler. Örneğin, kullanıcı 5 yazıp
enter’a basarsa, tahmin şöyle görünür: 5\n. \n “yeni satırı”
temsil eder. (Windows’ta enter’a basmak satır başı ve yeni satır,
\r\n ile sonuçlanır.) trim metodu \n veya \r\n’i ortadan kaldırarak
sadece 5 olmasını sağlar.
Metinler (strings) üzerindeki parse metodu,
bir metni başka bir türe dönüştürür. Burada, onu bir metinden bir sayıya
dönüştürmek için kullanıyoruz. let tahmin: u32 diyerek Rust’a tam
olarak istediğimiz sayı türünü söylemeliyiz. tahmin’den sonraki
iki nokta üst üste (:), Rust’a değişkenin türünü belirteceğimizi söyler.
Rust’ın birkaç yerleşik sayı türü vardır; burada gördüğünüz u32 işaretsiz,
32 bitlik bir tam sayıdır. Küçük pozitif bir sayı için iyi bir
varsayılan seçimdir. Diğer sayı türlerini Bölüm 3’te
öğreneceksiniz.
Ek olarak, bu örnek programdaki u32 bildirmi (annotation) ve gizli_sayi ile
yapılan karşılaştırma, Rust’ın gizli_sayi’nın da bir u32 olması gerektiği
çıkarımını yapacağı anlamına gelir. Yani, artık karşılaştırma aynı
türden iki değer arasında olacaktır!
parse metodu yalnızca mantıksal olarak sayılara dönüştürülebilen
karakterler üzerinde çalışacaktır ve bu nedenle kolayca hatalara neden olabilir.
Örneğin, metin A👍% içerseydi, bunu bir sayıya dönüştürmenin hiçbir yolu olmazdı.
Başarısız olabileceği için parse metodu da read_line metodunun yaptığı gibi
(daha önce “Potansiyel Bir Hatayı Result ile Ele Almak” kısmında tartışılmıştı) bir Result
türü döndürür. Bu Result’ı, expect metodunu tekrar kullanarak aynı
şekilde ele alacağız. Eğer parse metodu, metinden bir sayı oluşturamadığı
için bir Err Result varyantı döndürürse, expect çağrısı oyunu çökertecek
(crash) ve ona verdiğimiz mesajı yazdıracaktır. Eğer parse, metni
başarıyla bir sayıya dönüştürebilirse, Result’ın Ok varyantını döndürür;
ve expect, Ok değerinden istediğimiz numarayı döndürecektir.
Programı şimdi çalıştıralım:
$ cargo run
Compiling tahmin_oyunu v0.1.0 (file:///projects/tahmin_oyunu)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s
Running `target/debug/tahmin_oyunu`
Sayıyı tahmin et!
Gizli sayı: 58
Lütfen tahmininizi girin.
76
Tahmininiz: 76
Çok büyük!
Güzel! Tahminden önce boşluklar eklenmiş olmasına rağmen, program yine de kullanıcının 76 tahmininde bulunduğunu anladı (figured out). Farklı türdeki girdilerle farklı davranışları doğrulamak için programı birkaç kez çalıştırın: Sayıyı doğru tahmin edin, çok büyük bir sayı tahmin edin ve çok küçük bir sayı tahmin edin.
Şu anda oyunun büyük bir kısmı çalışıyor, ancak kullanıcı yalnızca bir tahmin yapabiliyor. Bir döngü (loop) ekleyerek bunu değiştirelim!
Döngüyle Birden Fazla Tahmine İzin Vermek
loop anahtar kelimesi sonsuz bir döngü oluşturur. Kullanıcılara sayıyı tahmin
etmeleri için daha fazla şans vermek adına bir döngü ekleyeceğiz:
Dosya adı: src/main.rs
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("Sayıyı tahmin et!");
let gizli_sayi = rand::thread_rng().gen_range(1..=100);
// --snip--
println!("Gizli sayı: {gizli_sayi}");
loop {
println!("Lütfen tahmininizi girin.");
// --snip--
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı yazın!");
println!("Tahmininiz: {tahmin}");
match tahmin.cmp(&gizli_sayi) {
Ordering::Less => println!("Çok küçük!"),
Ordering::Greater => println!("Çok büyük!"),
Ordering::Equal => println!("Kazandınız!"),
}
}
}
Gördüğünüz gibi, tahmini giriş isteminden itibaren her şeyi bir döngü içine taşıdık. Döngünün içindeki satırların her birini fazladan dört boşluk (space) girintilediğinizden (indent) emin olun ve programı tekrar çalıştırın. Program artık sonsuza dek başka bir tahmin isteyecektir, ki bu aslında yeni bir sorun ortaya çıkarır. Kullanıcı çıkamayacak gibi görünüyor!
Kullanıcı her zaman ctrl-C klavye kısayolunu
kullanarak programı durdurabilir. Ancak, “Tahmini Gizli Sayıyla Karşılaştırmak” kısmındaki parse
tartışmasında bahsedildiği gibi, bu doyumsuz canavardan kaçmanın başka bir yolu daha var:
Eğer kullanıcı sayı olmayan bir girdi girerse, program çökecektir (crash).
Aşağıda gösterildiği gibi kullanıcının çıkabilmesini (quit) sağlamak için
bundan faydalanabiliriz:
$ cargo run
Compiling tahmin_oyunu v0.1.0 (file:///projects/tahmin_oyunu)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.23s
Running `target/debug/tahmin_oyunu`
Sayıyı tahmin et!
Gizli sayı: 59
Lütfen tahmininizi girin.
45
Tahmininiz: 45
Çok küçük!
Lütfen tahmininizi girin.
60
Tahmininiz: 60
Çok büyük!
Lütfen tahmininizi girin.
59
Tahmininiz: 59
Kazandınız!
Lütfen tahmininizi girin.
cikis
thread 'main' panicked at src/main.rs:28:47:
Lütfen bir sayı yazın!: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
cikis (quit) yazmak oyundan çıkmanızı sağlayacaktır, ancak
fark edeceğiniz üzere başka herhangi bir sayısal olmayan girdi de aynısını yapacaktır.
Bu, en hafif tabirle ideal bir durum değildir (suboptimal); oyunun, doğru
sayı tahmin edildiğinde de durmasını istiyoruz.
Doğru Tahminden Sonra Çıkmak
Kullanıcı kazandığında oyundan çıkmasını sağlamak için bir break
(kır) ifadesi ekleyerek oyunu programlayalım:
Dosya adı: src/main.rs
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("Sayıyı tahmin et!");
let gizli_sayi = rand::thread_rng().gen_range(1..=100);
println!("Gizli sayı: {gizli_sayi}");
loop {
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
let tahmin: u32 = tahmin.trim().parse().expect("Lütfen bir sayı yazın!");
println!("Tahmininiz: {tahmin}");
// --snip--
match tahmin.cmp(&gizli_sayi) {
Ordering::Less => println!("Çok küçük!"),
Ordering::Greater => println!("Çok büyük!"),
Ordering::Equal => {
println!("Kazandınız!");
break;
}
}
}
}
Kazandınız! ifadesinden sonra break satırını eklemek, kullanıcı gizli
sayıyı doğru tahmin ettiğinde programın döngüden çıkmasını sağlar.
Döngüden çıkmak aynı zamanda programdan çıkmak anlamına da gelir,
çünkü döngü main’in son kısmıdır.
Geçersiz Girdiyi Ele Almak
Oyunun davranışını daha da iyileştirmek için, kullanıcı sayısal olmayan
bir girdi girdiğinde programı çökertmek (crash) yerine, kullanıcının tahmin etmeye
devam edebilmesi için oyunun sayı olmayan veriyi görmezden gelmesini sağlayalım.
Bunu Liste 2-5’te gösterildiği gibi, tahmin’in String’den u32’ye
dönüştürüldüğü satırı değiştirerek yapabiliriz.
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("Sayıyı tahmin et!");
let gizli_sayi = rand::thread_rng().gen_range(1..=100);
println!("Gizli sayı: {gizli_sayi}");
loop {
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
// --snip--
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
let tahmin: u32 = match tahmin.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("Tahmininiz: {tahmin}");
// --snip--
match tahmin.cmp(&gizli_sayi) {
Ordering::Less => println!("Çok küçük!"),
Ordering::Greater => println!("Çok büyük!"),
Ordering::Equal => {
println!("Kazandınız!");
break;
}
}
}
}
Bir hata durumunda programı çökertmekten ziyade hatayı yönetmeye (handle the error)
geçmek için, bir expect çağrısından çıkıp match ifadesine geçiş yapıyoruz.
parse’ın bir Result türü döndürdüğünü ve Result’ın Ok ile Err varyantlarına
sahip bir enum olduğunu hatırlayın. Burada, tıpkı cmp metodunun Ordering sonucunda
yaptığımız gibi bir match ifadesi kullanıyoruz.
Eğer parse metni başarıyla bir sayıya dönüştürebilirse, elde edilen
sayıyı içeren bir Ok değeri döndürür. O Ok değeri, ilk kolun deseniyle
eşleşecek ve match ifadesi, sadece parse’ın ürettiği ve Ok değerinin
içine koyduğu sayi (num) değerini döndürecektir. Bu sayı, oluşturduğumuz yeni
tahmin değişkeninde tam da istediğimiz yere gelecektir.
Eğer parse metni bir sayıya dönüştüremezse (not able), hata hakkında daha
fazla bilgi barındıran bir Err değeri döndürür. Err değeri, ilk match kolundaki
Ok(sayi) deseni ile eşleşmez, ancak ikinci koldaki Err(_) deseni ile eşleşir.
Alt çizgi (_), her şeyi yakalayan (catch-all) bir değerdir; bu örnekte, içlerinde
ne bilgi olursa olsun tüm Err değerleriyle eşleşmek istediğimizi söylüyoruz. Böylece,
program ikinci kolun kodu olan continue’yu (devam et) çalıştırır; bu da
programa loop’un bir sonraki döngüsüne (iteration) geçmesini ve
başka bir tahmin istemesini söyler. Yani etkin olarak, program
parse’ın karşılaşabileceği tüm hataları yok sayar!
Artık programdaki her şey beklendiği gibi çalışmalıdır. Deneyelim:
$ cargo run
Compiling tahmin_oyunu v0.1.0 (file:///projects/tahmin_oyunu)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
Running `target/debug/tahmin_oyunu`
Sayıyı tahmin et!
Gizli sayı: 61
Lütfen tahmininizi girin.
10
Tahmininiz: 10
Çok küçük!
Lütfen tahmininizi girin.
99
Tahmininiz: 99
Çok büyük!
Lütfen tahmininizi girin.
foo
Lütfen tahmininizi girin.
61
Tahmininiz: 61
Kazandınız!
Harika! Son bir küçük ayarlama (tweak) ile tahmin oyununu bitireceğiz. Programın hâlâ
gizli sayıyı yazdırdığını hatırlayın. Bu, test için iyi çalıştı, ancak oyunu
mahvediyor. Gizli sayıyı çıktı olarak veren println! ifadesini silelim.
Liste 2-6 son kodu göstermektedir.
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("Sayıyı tahmin et!");
let gizli_sayi = rand::thread_rng().gen_range(1..=100);
loop {
println!("Lütfen tahmininizi girin.");
let mut tahmin = String::new();
io::stdin()
.read_line(&mut tahmin)
.expect("Satır okunamadı");
let tahmin: u32 = match tahmin.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("Tahmininiz: {tahmin}");
match tahmin.cmp(&gizli_sayi) {
Ordering::Less => println!("Çok küçük!"),
Ordering::Greater => println!("Çok büyük!"),
Ordering::Equal => {
println!("Kazandınız!");
break;
}
}
}
}
Bu noktada tahmin oyununu başarıyla inşa ettiniz. Tebrikler!
Özet
Bu proje sizi birçok yeni Rust kavramıyla uygulamalı olarak
tanıştırmanın bir yoluydu: let, match, fonksiyonlar, harici crate’lerin
kullanımı ve daha fazlası. Sonraki birkaç bölümde bu kavramlar
hakkında daha fazla ayrıntı öğreneceksiniz. Bölüm 3, çoğu
programlama dilinin sahip olduğu değişkenler, veri türleri ve
fonksiyonlar gibi kavramları ele alır ve bunların Rust’ta
nasıl kullanılacağını gösterir. Bölüm 4, Rust’ı diğer dillerden
farklı kılan bir özellik olan sahipliği araştırır.
Bölüm 5 struct’ları (yapıları) ve metot sözdizimini (method syntax) tartışırken,
Bölüm 6 enum’ların nasıl çalıştığını açıklar.