Değişkenler ve Değiştirilebilirlik
“Değerleri Değişkenlerle Saklamak” bölümünde değindiğimiz gibi, değişkenler varsayılan olarak değiştirilemezdir (immutable). Bu, Rust’ın kodunuzu dilin sunduğu güvenlik ve kolay eşzamanlılık (concurrency) avantajlarından yararlanacak biçimde yazmanız için size yaptığı pek çok küçük yönlendirmeden biridir. Bununla birlikte, değişkenlerinizi değiştirilebilir (mutable) yapma seçeneğiniz her zaman vardır. Gelin Rust’ın sizi değiştirilemezliği tercih etmeye nasıl ve neden teşvik ettiğini, ayrıca bazen bundan neden vazgeçmek isteyebileceğinizi inceleyelim.
Bir değişken değiştirilemez olduğunda, bir isme bir değer bağlandıktan sonra o değeri bir daha değiştiremezsiniz. Bunu görmek için projects dizininizde cargo new degiskenler komutunu kullanarak degiskenler adında yeni bir proje oluşturun.
Ardından yeni degiskenler dizininizde src/main.rs dosyasını açın ve içeriğini şimdilik derlenmeyecek olan aşağıdaki kodla değiştirin:
Dosya adı: src/main.rs
fn main() {
let x = 5;
println!("x'in değeri: {x}");
x = 6;
println!("x'in değeri: {x}");
}
Dosyayı kaydedin ve cargo run komutuyla programı çalıştırın. Aşağıdaki çıktıda gösterildiği gibi bir değiştirilemezlik hatası mesajı almalısınız:
$ cargo run
Compiling degiskenler v0.1.0 (file:///projects/degiskenler)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("x'in değeri: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `degiskenler` (bin "degiskenler") due to 1 previous error
Bu örnek, derleyicinin programlarınızdaki hataları bulmanıza nasıl yardımcı olduğunu gösterir. Derleyici hataları zaman zaman can sıkıcı olabilir; ancak bu hatalar yalnızca programınızın yapmasını istediğiniz işi henüz güvenli bir biçimde gerçekleştirmediği anlamına gelir, sizin iyi bir programcı olmadığınız anlamına gelmez! Deneyimli Rust geliştiricileri (Rustaceans) bile hâlâ derleyici hataları alırlar.
Değiştirilemez x değişkenine ikinci bir değer atamaya çalıştığınız için cannot assign twice to immutable variable `x` (x değiştirilemez değişkenine ikinci kez atama yapılamaz) hata mesajını aldınız.
Not: Rust derleyicisinin hata mesajları sorunu çözmeniz için gereken ipuçlarını adım adım sunar: error[E0384] hatanın kodunu, --> src/main.rs:4:5 hatanın tam konumunu (4. satır, 5. sütun), altındaki işaretler ise ilk atamanın nerede yapıldığını (first assignment to `x`) ve kural ihlalinin hangi satırda gerçekleştiğini gösterir. Ayrıca help: (yardım) satırı let mut x = 5; şeklinde doğrudan uygulanabilecek bir çözüm önerir. Bu hata kodu hakkında daha ayrıntılı açıklamayı ve ek örnekleri görmek isterseniz terminalinizde rustc --explain E0384 komutunu çalıştırabilirsiniz.
Değiştirilemez olarak tanımlanmış bir değeri değiştirmeye çalıştığımızda derleme zamanı (compile-time) hataları almamız son derece önemlidir; çünkü tam da bu durum kodda hatalara (bug) yol açabilir. Kodumuzun bir bölümü bir değerin asla değişmeyeceği varsayımıyla çalışırken başka bir bölümü o değeri değiştirirse, ilk bölümün tasarlandığı işi doğru yapmaması olasıdır. Bu tür bir hatanın nedenini sonradan bulmak çok zor olabilir; özellikle de ikinci kod parçası değeri yalnızca bazen değiştiriyorsa. Rust derleyicisi, bir değerin değişmeyeceğini belirttiğinizde gerçekten değişmeyeceğini garanti eder; dolayısıyla bunu kendiniz takip etmek zorunda kalmazsınız. Böylece kodunuz üzerinden akıl yürütmek çok daha kolaylaşır.
Ancak değiştirilebilirlik çok faydalı olabilir ve kod yazmayı daha pratik hâle getirebilir. Değişkenler varsayılan olarak değiştirilemez olsa da, Bölüm 2’de yaptığınız gibi değişken adının önüne mut ekleyerek onları değiştirilebilir yapabilirsiniz. mut eklemek aynı zamanda kodun başka bölümlerinin bu değişkenin değerini değiştireceğini açıkça göstererek kodu gelecekte okuyacak kişilere niyetinizi iletir.
Örneğin src/main.rs dosyasını aşağıdaki gibi değiştirelim:
Dosya adı: src/main.rs
fn main() {
let mut x = 5;
println!("x'in değeri: {x}");
x = 6;
println!("x'in değeri: {x}");
}
Programı şimdi çalıştırdığımızda şu çıktıyı elde ederiz:
$ cargo run
Compiling degiskenler v0.1.0 (file:///projects/degiskenler)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
Running `target/debug/degiskenler`
x'in değeri: 5
x'in değeri: 6
mut kullanıldığında x değişkenine bağlı değeri 5’ten 6’ya değiştirmemize izin verilir. Sonuç olarak değiştirilebilirliği kullanıp kullanmamaya karar vermek size kalmıştır ve içinde bulunduğunuz durumda neyin en anlaşılır olduğunu düşündüğünüze bağlıdır.
Sabitleri Bildirmek
Değiştirilemez değişkenler gibi, sabitler (constants) de bir isme bağlı olan ve değiştirilmesine izin verilmeyen değerlerdir; ancak sabitler ile değişkenler arasında birkaç önemli fark vardır.
İlk olarak, sabitlerle birlikte mut kullanmanıza izin verilmez. Sabitler yalnızca varsayılan olarak değil, her zaman değiştirilemezdir. Sabitleri let anahtar kelimesi yerine const anahtar kelimesini kullanarak bildirirsiniz ve değerin türü mutlaka açıkça belirtilmelidir. Türleri ve tür bildirimlerini (type annotations) bir sonraki bölüm olan “Veri Türleri” kısmında ele alacağız; bu yüzden şimdilik ayrıntılar konusunda endişelenmeyin, yalnızca sabitlerde türü her zaman belirtmeniz gerektiğini bilin.
Sabitler, küresel kapsam (global scope) da dahil olmak üzere herhangi bir kapsamda bildirilebilir; bu da onları kodun pek çok bölümünün bilmesi gereken ortak değerler için kullanışlı kılar.
Son fark ise sabitlere yalnızca bir sabit ifadenin (constant expression), yani derleme zamanında değerlendirilebilen bir ifadenin atanabilmesidir; yalnızca çalışma zamanında (runtime) hesaplanabilecek bir değer sabite atanamaz.
İşte bir sabit bildirimi örneği:
#![allow(unused)]
fn main() {
const SANIYE_CINSINDEN_UC_SAAT: u32 = 60 * 60 * 3;
}
Sabitin adı SANIYE_CINSINDEN_UC_SAAT’tir; değeri ise 60 (bir dakikadaki saniye sayısı), 60 (bir saatteki dakika sayısı) ve 3 (bu programda saymak istediğimiz saat sayısı) sayılarının çarpımı olarak belirlenmiştir. Rust’ın sabitler için adlandırma geleneği, tüm harfleri büyük yazmak ve kelimeler arasında alt çizgi kullanmaktır (SCREAMING_SNAKE_CASE). Derleyici, derleme zamanında belirli bir dizi işlemi hesaplayabilir; bu sayede sabiti doğrudan 10_800 değerine eşitlemek yerine, anlaşılması ve doğrulanması çok daha kolay bir biçimde yazmayı seçebiliriz. Sabitleri bildirirken hangi işlemlerin kullanılabileceği hakkında daha fazla bilgi için Rust Referansı’nın sabit değerlendirme bölümüne bakabilirsiniz.
Sabitler, bildirildikleri kapsam içinde programın çalıştığı süre boyunca geçerlidir. Bu özellik; bir oyundaki herhangi bir oyuncunun kazanabileceği azami puan veya ışık hızı gibi, uygulama alanınızda programın birden fazla bölümünün bilmesi gerekebilecek değerler için sabitleri yararlı kılar.
Programınız boyunca doğrudan koda yazılmış sabit değerleri const ile isimlendirmek, bu değerin anlamını kodun gelecekteki bakımcılarına iletmek açısından faydalıdır. Ayrıca bu değerin gelecekte güncellenmesi gerektiğinde kodunuzda değiştirmeniz gereken yalnızca tek bir yer olmasını sağlar.
Not: Değiştirilemez bir let değişkeni ile const arasındaki pratik farkı
şöyle özetleyebiliriz: let yalnızca fonksiyon ve blok kapsamlarında
tanımlanabilir, derleyicinin tür çıkarımından yararlanabilir ve kullanıcı
girdisi ya da dosya okuma gibi çalışma zamanında üretilen bir değeri (sonradan
değiştirilmemek üzere) bağlayabilir. const ise modül ve dosya düzeyinde
küresel olarak da bildirilebilir, açık tür bildirimi zorunludur ve değeri
derleme zamanında hesaplanabilen bir ifade olmalıdır. Sabit için bütün
kullanımların paylaştığı tek bir bellek adresi garanti edilmez. Rust’ta ayrıca
static anahtar kelimesiyle bildirilen ve program boyunca bellekte sabit bir
adreste tutulan küresel değişkenler de vardır; bunları Bölüm 20’de güvenli
olmayan Rust’ı incelerken ele alacağız.
Gölgeleme
Bölüm 2’deki tahmin oyunu uygulamasında gördüğünüz gibi, daha önce tanımlanmış bir değişkenle aynı isimde yeni bir değişken bildirebilirsiniz. Rust geliştiricileri ilk değişkenin ikincisi tarafından gölgelendiğini (shadowed) söyler; bu da değişkenin adını kullandığınızda derleyicinin göreceği değişkenin artık ikincisi olduğu anlamına gelir. Özünde ikinci değişken ilkini gölgede bırakır ve kendisi de gölgelenene ya da bulunduğu kapsam sona erene kadar o değişken adının tüm kullanımlarını devralır. Bir değişkeni, aynı değişken adını kullanıp let anahtar kelimesini tekrarlayarak aşağıdaki gibi gölgeleyebiliriz:
Dosya adı: src/main.rs
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("İç kapsamdaki x'in değeri: {x}");
}
println!("x'in değeri: {x}");
}
Bu program önce x değişkenini 5 değerine bağlar. Ardından let x = kullanımını tekrarlayarak yeni bir x değişkeni oluşturur; önceki değeri alıp üzerine 1 ekler ve böylece x’in değeri 6 olur. Sonrasında süslü parantezlerle oluşturulan bir iç kapsamda, üçüncü let deyimi de x’i gölgeleyerek yeni bir değişken oluşturur ve önceki değeri 2 ile çarparak x’e 12 değerini verir. Bu iç kapsam sona erdiğinde içteki gölgeleme biter ve dış kapsamdaki, değeri hâlâ 6 olan x yeniden görünür hâle gelir. Burada bir değişkenin değeri geri alınmaz; aynı adlı farklı bağlamalar arasında kapsam değişir. Programı çalıştırdığımızda şu çıktıyı alırız:
$ cargo run
Compiling degiskenler v0.1.0 (file:///projects/degiskenler)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s
Running `target/debug/degiskenler`
İç kapsamdaki x'in değeri: 12
x'in değeri: 6
Gölgeleme, bir değişkeni mut olarak işaretlemekten farklıdır; çünkü let anahtar kelimesini kullanmadan bu değişkene yanlışlıkla yeniden değer atamaya çalışırsak derleme zamanı hatası alırız. let kullanarak bir değer üzerinde birkaç dönüştürme işlemi gerçekleştirebilir, ancak bu dönüştürmeler tamamlandıktan sonra değişkenin yine değiştirilemez kalmasını sağlayabiliriz.
mut ile gölgeleme arasındaki diğer önemli fark ise şudur: let anahtar kelimesini tekrar kullandığımızda gerçekte yepyeni bir değişken oluşturduğumuz için, aynı ismi korurken değerin türünü değiştirebiliriz. Örneğin programımız kullanıcıdan, metin parçaları arasında kaç boşluk istediğini boşluk karakterleri girerek göstermesini istesin; biz de ardından bu girdiyi bir sayı olarak saklamak isteyelim:
fn main() {
let bosluklar = " ";
let bosluklar = bosluklar.len();
}
İlk bosluklar değişkeni bir string dilimi (&str) türündedir, ikinci bosluklar değişkeni ise bir sayı (usize) türündedir. Gölgeleme böylece bizi bosluklar_metin ve bosluklar_sayi gibi farklı isimler türetmek zorunda kalmaktan kurtarır; bunun yerine daha yalın olan bosluklar ismini yeniden kullanabiliriz. Oysa aynı işi burada gösterildiği gibi mut kullanarak yapmaya çalışırsak derleme zamanı hatası alırız:
fn main() {
let mut bosluklar = " ";
bosluklar = bosluklar.len();
}
Hata mesajı, bir değişkenin türünü değiştirmemize izin verilmediğini (mismatched types — uyuşmayan türler) belirtir:
$ cargo run
Compiling degiskenler v0.1.0 (file:///projects/degiskenler)
error[E0308]: mismatched types
--> src/main.rs:3:17
|
2 | let mut bosluklar = " ";
| ----- expected due to this value
3 | bosluklar = bosluklar.len();
| ^^^^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `degiskenler` (bin "degiskenler") due to 1 previous error
Not: Buradaki temel ayrımı netleştirelim: mut mevcut değişkenin bellekteki değerini aynı türde kalmak koşuluyla günceller; gölgeleme (let) ise aynı ismi kullanan ve gerekirse farklı bir türde de olabilen yeni bir değişken oluşturur. Ayrıca örnekteki .len() metodunun metindeki karakter sayısını değil, UTF-8 bayt uzunluğunu (usize) döndürdüğüne dikkat edin: ASCII boşluk karakterlerinin (' ') her biri 1 bayt olduğundan " ".len() ifadesi 3 döndürür; ancak 'ç', 'ğ', 'ı', 'ö', 'ş', 'ü' gibi Türkçe harfler UTF-8 kodlamasında 2 bayt yer kapladığından örneğin "şğü".len() çağrısı 6 döndürür (bu konuyu Bölüm 8’de ayrıntılarıyla inceleyeceğiz).
Artık değişkenlerin nasıl çalıştığını incelediğimize göre, değişkenlerin sahip olabileceği diğer veri türlerine göz atalım.