Rust'ta değişkenler let ile tanımlanır ve varsayılan olarak değişmezdir (immutable): bir isme değer bağladıktan sonra o değeri değiştiremezsiniz. Değiştirmek istiyorsanız let mut yazarsınız. Aynı ismi yeni bir let ile tekrar tanımlamak ise gölgeleme (shadowing) olur ve tipi bile değiştirebilir. Programın her yerinde geçerli, hiç değişmeyen değerler için const, tek bir bellek adresine sahip global değerler için static kullanılır.

Bu yazıda bu beş kavramı, aralarındaki farkları ve derleyicinin her biri için verdiği gerçek hata ve uyarı mesajlarını Rust 1.99.1 çıktılarıyla gösteriyoruz. Ayrıca Rust 2024 edition'ın static mut konusunda getirdiği sıkılaştırmayı ve sonradan ilk değer verme kurallarını ele alıyoruz. Kurulum yapmadıysanız önce Rust kurulumu yazısına, serinin bütününe Rust öğrenme rehberi sayfasından bakabilirsiniz.

let ve varsayılan değişmezlik

let ve varsayılan değişmezlik

Aşağıdaki program x değişkenine önce 5 bağlıyor, sonra 6 atamaya çalışıyor. Çoğu dilde sorunsuz çalışacak bu kod Rust'ta derlenmez:

rust
fn main() {
    let x = 5;
    println!("x: {x}");
    x = 6;
    println!("x: {x}");
}
// error[E0384]: cannot assign twice to immutable variable `x`
// help: consider making this binding mutable

Derleyici yalnızca hatayı göstermekle kalmaz, ilk atamanın yerini işaretler ve mut eklemenizi önerir. The Book bu tasarımı şöyle gerekçelendirir: kodun bir bölümü bir değerin hiç değişmeyeceğini varsayarken başka bir bölüm onu değiştirirse, ilk bölüm beklendiği gibi çalışmayabilir ve bu tür hataların kaynağını sonradan bulmak zordur, özellikle değer yalnızca bazen değişiyorsa. Rust, bir değerin değişmeyeceğini söylediğinizde bunun gerçekten öyle kalacağını derleme zamanında garanti eder; siz de bunu takip etmek zorunda kalmazsınız.

Değişmezlik Rust'ın güvenli eşzamanlılık hedefiyle de ilgilidir. Değişmeyen bir değeri birden çok iş parçacığının okuması sorun yaratmaz; sorun, değerin aynı anda hem okunup hem değiştirilmesidir. Rust bu kuralı daha genel hâliyle ödünç alma (borrowing) sisteminde de uygular; ayrıntılar referanslar ve borrowing yazısında.

mut ile değiştirilebilir değişken

mut anahtar kelimesi değişkeni değiştirilebilir yapar ve kodu okuyan kişiye bu değerin ileride değişeceğini haber verir. Derleyici, mut ile işaretlediğiniz ama hiç değiştirmediğiniz değişkenler için uyarı verir. Aynı şekilde tanımlayıp hiç kullanmadığınız değişkenler için de uyarı alırsınız; isim alt çizgiyle başlıyorsa bu uyarı verilmez:

rust
fn main() {
    let mut sayac = 0;
    sayac += 1;
    sayac += 1;
    println!("sayaç: {sayac}");

    let mut gereksiz = 10;          // uyarı: variable does not need to be mutable
    println!("gereksiz: {gereksiz}");

    let kullanilmayan = 3;          // uyarı: unused variable: `kullanilmayan`
    let _bilerek = 4;               // alt çizgi: uyarı yok
}
// çıktı: sayaç: 2
// çıktı: gereksiz: 10

Bu uyarılar, rustc'nin varsayılan olarak açık unused_mut ve unused_variables lint'lerinden gelir. Uyarı programın derlenmesini engellemez ama çoğu zaman bir mantık hatasına işaret eder: değiştirmeyi planladığınız bir değişkeni unuttuğunuzu ya da yanlış değişkeni değiştirdiğinizi gösterebilir. cargo fix komutu gereksiz mut gibi açık çözümü olan uyarıları otomatik düzeltebilir.

Fonksiyon parametreleri de varsayılan olarak değişmezdir. Reference'a göre mut yalnızca hemen ardından gelen parametreye uygulanır. Aynı kural desenle tanımlanan değişkenler için de geçerlidir: bir tuple'ı parçalarken bazı parçaları değişmez, bazılarını değiştirilebilir yapabilirsiniz. Desenlerin tamamı için pattern söz dizimi yazısına bakın.

rust
fn yasi_artir(mut yas: u32) -> u32 {
    yas += 1;
    yas
}

fn main() {
    let (ad, mut yas) = ("Ayşe", 30);
    yas += 1;
    println!("{ad} {yas}");
    println!("{}", yasi_artir(yas));
    println!("{yas}");
}
// çıktı: Ayşe 31
// çıktı: 32
// çıktı: 31

Son satırda yas hâlâ 31'dir: u32 kopyalanan bir tip olduğu için fonksiyon kendi kopyasını değiştirdi. Parametredeki mut, çağıran tarafın değişkenini değil, fonksiyonun içindeki yerel kopyayı değiştirilebilir yapar. Çağıranın değişkenini değiştirmek için &mut referansı gerekir.

Sonradan ilk değer verme

let ile bir değişkeni değer vermeden tanımlayıp ilk değerini daha sonra atayabilirsiniz. Bu, değişkenin mut olmasını gerektirmez; çünkü bu bir yeniden atama değil, ilk atamadır. Reference'a göre yerel değişkenler, ulaşılabilir bütün kontrol akışı yollarında ilk değerini almadan kullanılamaz:

rust
fn main() {
    let not: u32 = 72;
    let durum;
    if not >= 50 {
        durum = "geçti";
    } else {
        durum = "kaldı";
    }
    println!("{durum}");
}
// çıktı: geçti

else dalını silerseniz durum bazı yollarda ilk değerini almadan kullanılmış olur ve derleyici reddeder. Mesajın bir notu ilginçtir: derleyici dalların koşullarını değerlendirmez, yalnızca olası yolları inceler. Yani not değeri 72 olarak sabit olsa bile hata verilir:

rust
// else dalı olmadan:
// error[E0381]: used binding `durum` is possibly-uninitialized
// note: when checking initialization, the compiler describes possible control-flow paths
//       without evaluating whether branch conditions can actually have the values shown

Pratikte bu kalıp yerine çoğu zaman let durum = if not >= 50 { "geçti" } else { "kaldı" }; yazılır, çünkü if Rust'ta bir ifadedir ve değer üretir. Bu konu kontrol akışı yazısında ayrıntılı işleniyor.

Shadowing (gölgeleme)

Shadowing (gölgeleme): Yeni değişken oluşturur mu, Tip değişebilir mi, Sonradan değiştirilebilir mi, let gerekir mi

Aynı isimde yeni bir değişkeni yeniden let ile tanımladığınızda ilk değişken gölgelenir: o noktadan sonra derleyici bu ismi gördüğünde yeni değişkeni kullanır. Gölgeleme, iç kapsamda yapılırsa yalnızca o kapsam bitene kadar sürer:

rust
fn main() {
    let x = 5;
    let x = x + 1;
    {
        let x = x * 2;
        println!("iç kapsamda x: {x}");
    }
    println!("dış kapsamda x: {x}");

    let bosluklar = "   ";
    let bosluklar = bosluklar.len();
    println!("boşluk sayısı: {bosluklar}");
}
// çıktı: iç kapsamda x: 12
// çıktı: dış kapsamda x: 6
// çıktı: boşluk sayısı: 3

Program önce x'e 5 bağlar, sonra yeni bir x oluşturup 6 yapar. Süslü parantezle açılan iç kapsamda üçüncü bir x değeri 12'ye çıkarır; kapsam bitince bu gölgeleme sona erer ve x yeniden 6 olur. Son iki satır ise gölgelemenin asıl gücünü gösterir: ilk bosluklar bir metin (&str), ikincisi bir sayıdır (usize). Böylece bosluklar_str ve bosluklar_sayi gibi ayrı isimler uydurmak zorunda kalmazsınız.

Aynı şeyi mut ile yapmaya çalışırsanız derleyici izin vermez, çünkü mut değeri değiştirir ama tipi değiştiremez:

rust
fn main() {
    let mut bosluklar = "   ";
    bosluklar = bosluklar.len();
    println!("{bosluklar}");
}
// error[E0308]: mismatched types
// expected `&str`, found `usize`
ÖzellikmutShadowing
Yeni değişken oluşturur muHayır, aynı değişkenEvet, aynı isimde yeni değişken
Tip değişebilir miHayırEvet
Sonradan değiştirilebilir miEvetYalnızca yeni değişken mut ise
let gerekir miHayır, düz atamaEvet, her seferinde let
Kapsam bitinceDeğişken kapsamla birlikte biterÖnceki değişken yeniden görünür

Gölgelemenin güvenlik açısından bir avantajı vardır: bir değeri birkaç adımda dönüştürüp sonra değişmez bırakabilirsiniz. Dönüşümler bittikten sonra yanlışlıkla let olmadan atama yaparsanız derleme hatası alırsınız. Dezavantajı ise uzun fonksiyonlarda aynı ismin farklı anlamlar taşımasının okumayı zorlaştırabilmesidir; gölgelemeyi bir değerin dönüştürülmüş hâli için kullanmak iyi bir alışkanlıktır.

const: derleme zamanı sabitleri

Sabitler de değişmez değişkenler gibi bir isme bağlı ve değiştirilemeyen değerlerdir, ama birkaç önemli fark vardır. const ile mut kullanılamaz; sabitler her zaman değişmezdir. Tip belirtmek zorunludur. Sabitler global kapsam dahil her kapsamda tanımlanabilir. Değerleri yalnızca derleme zamanında hesaplanabilen bir sabit ifade olabilir; çalışma zamanında hesaplanan bir sonuç sabite atanamaz. İsimlendirme geleneği, kelimeler arasında alt çizgi olan büyük harflerdir:

rust
const SAATTE_SANIYE: u32 = 60 * 60;
const UC_SAAT: u32 = SAATTE_SANIYE * 3;
static SITE_ADI: &str = "keydal.tr";

fn main() {
    const YEREL_LIMIT: usize = 3;
    println!("{UC_SAAT} saniye");
    println!("{SITE_ADI}");
    let dizi = [0u8; YEREL_LIMIT];
    println!("{dizi:?}");
}
// çıktı: 10800 saniye
// çıktı: keydal.tr
// çıktı: [0, 0, 0]

Derleyici sınırlı bir işlem kümesini derleme zamanında değerlendirebilir; bu yüzden 10800 yazmak yerine 60 * 60 * 3 yazarak değerin nereden geldiğini okunur kılabilirsiniz. Sabitler başka sabitlere de başvurabilir. Örnekteki YEREL_LIMIT bir fonksiyonun içinde tanımlanmış ve dizi uzunluğu olarak kullanılmıştır; dizi uzunlukları derleme zamanında bilinmek zorunda olduğu için burada let ile tanımlanmış bir değişken kullanamazdınız. Tipi yazmayı unutursanız derleyici bir öneriyle birlikte reddeder:

rust
const AZAMI_PUAN = 100;
// error: missing type for `const` item
// help: provide a type for the constant: `: i32`

Reference'a göre sabitler kullanıldıkları her yere satır içi olarak kopyalanır; aynı sabite yapılan referansların aynı bellek adresini göstermesi garanti edilmez. Programın uygulama alanındaki sabit değerlere (bir oyuncunun alabileceği azami puan, ışık hızı gibi) isim vermek, değerin anlamını koda yansıtır ve değer değişirse tek bir yeri güncellemeniz yeter.

static: tek adresli global değerler

static: tek adresli global değerler: Tip yazmak, Değiştirilebilirlik, Değer, Kapsam

static öğeler sabitlere benzer, ancak Reference'a göre programda bir bellek alanını temsil ederler ve o static'e yapılan bütün referanslar aynı alanı gösterir. Static öğeler 'static ömrüne sahiptir, yani program boyunca yaşarlar ve program sonunda drop çağrılmaz. Değişmez bir static'in tipi iş parçacıkları arasında güvenle paylaşılabilmesi için Sync trait'ini uygulamalıdır. Reference, genel olarak const'u tercih etmenizi, static'i ise büyük miktarda veri saklarken, tek adres özelliğine ihtiyaç duyduğunuzda ya da iç değişebilirlik (interior mutability) gerektiğinde kullanmanızı önerir.

static mut ile değiştirilebilir bir global değer de tanımlanabilir, ama Reference'a göre bu tür değerleri okumak da yazmak da unsafe blok gerektirir; çünkü iki iş parçacığının aynı anda değiştirmesi veri yarışına yol açabilir. Rust 2024 edition bu konuda bir adım daha atmıştır: Edition Guide'a göre static mut değerine referans almayı yakalayan static_mut_refs lint'i 2024 edition'da varsayılan olarak deny seviyesindedir. Görünürde referans almasanız bile println! gibi makrolar gizlice referans aldığı için şu kod derlenmez:

rust
static mut SAYAC: u32 = 0;

fn main() {
    unsafe {
        SAYAC += 1;
        println!("{SAYAC}");
    }
}
// error: creating a shared reference to mutable static
// note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default

Edition Guide'ın önerdiği yol, mümkünse değiştirilebilir global durumdan kaçınmak, kaçınılamıyorsa iç değişebilirlik sağlayan bir tipi değişmez bir static içinde kullanmaktır. Basit bir sayaç için atomik tipler en kolay seçenektir ve hiç unsafe gerektirmez:

rust
use std::sync::atomic::{AtomicU32, Ordering};

static SAYAC: AtomicU32 = AtomicU32::new(0);

fn main() {
    SAYAC.fetch_add(1, Ordering::Relaxed);
    SAYAC.fetch_add(1, Ordering::Relaxed);
    println!("{}", SAYAC.load(Ordering::Relaxed));
}
// çıktı: 2

Atomik tipler ve Ordering seçenekleri Arc ve Mutex ile paylaşılan durum yazısında, unsafe blokların kuralları ise unsafe ve ham işaretçi yazısında anlatılıyor. 2024 edition'ın diğer değişiklikleri için Rust 2024 edition yazısına bakın.

Özellikletconststatic
Tip yazmakÇoğu zaman gerekmezZorunluZorunlu
Değiştirilebilirlikmut ileAslastatic mut ile, unsafe gerekir
DeğerÇalışma zamanında hesaplanabilirSabit ifadeSabit ifade
KapsamBlok içiHer kapsam, global dahilHer kapsam, global dahil
Bellek adresiYığın çerçevesindeKullanıldığı yere kopyalanırTek ve sabit adres
İsim geleneğisnake_caseBUYUK_HARFBUYUK_HARF

Sık yapılan hatalar

  • mut eklemeyi unutmak: E0384 hatası alırsınız; derleyicinin help: satırındaki öneriyi uygulayın ya da değişkenin gerçekten değişmesi gerekip gerekmediğini düşünün.
  • Her yere mut yazmak: Gereksiz mut uyarı üretir ve okuyucuya yanlış bilgi verir; değişmezlik varsayılan, değişebilirlik bilinçli bir tercih olmalıdır.
  • mut ile tip değiştirmeye çalışmak: E0308 alırsınız; tip değişecekse shadowing kullanın.
  • Sabitlerde tip yazmamak: const ve static için tip belirtmek zorunludur.
  • Çalışma zamanı değerini sabite atamak: Kullanıcı girdisi ya da dosyadan okunan değer gibi çalışma zamanında belli olan sonuçlar const olamaz; bunlar için let kullanın.
  • static mut ile global sayaç: 2024 edition'da referans alan her kullanım reddedilir; atomik tipleri ya da kilit tabanlı tipleri tercih edin.

Sık Sorulan Sorular

Rust neden değişkenleri varsayılan olarak değişmez yapıyor?

Bir değerin değişmeyeceğini varsayan kodla onu değiştiren kod arasındaki uyumsuzluktan doğan hataları derleme zamanında yakalamak için. Değişebilirliği açıkça mut ile istemeniz, kodu okuyanlara da hangi değerlerin değişeceğini gösterir.

Shadowing performansı etkiler mi?

Shadowing yeni bir değişken tanımlamaktır; belgelerde bunun bir çalışma zamanı maliyeti olduğuna dair bir bilgi yoktur. Asıl farkı anlamsaldır: tip değiştirebilir ve dönüşümden sonra değeri değişmez bırakır.

const mu static mi kullanmalıyım?

Reference'ın önerisine göre genellikle const. Tek bir bellek adresi gerektiğinde, büyük veri saklarken ya da atomik tip gibi iç değişebilirlik sağlayan bir tip kullanırken static seçin.

Alt çizgiyle başlayan değişken adının özel bir anlamı var mı?

Derleyici alt çizgiyle başlayan isimler için kullanılmayan değişken uyarısı vermez. Yalnızca _ yazmak ise değeri hiçbir isme bağlamaz; bu fark desenler konusunda önem kazanır.

let ile tanımlanan değişkenler bellekte nerede durur?

Reference'a göre yerel bir değişken değerini doğrudan yığın (stack) çerçevesinde tutar ve fonksiyona girildiğinde çerçevedeki bütün yerel değişkenler için yer ayrılır; değerler ise başlangıçta atanmamış durumdadır. Bu yüzden derleyici, bir değişkenin her yolda ilk değerini aldığını kanıtlayamazsa onu kullanmanıza izin vermez. String ya da Vec gibi tiplerin asıl içeriği ise heap üzerindedir; bu ayrım ownership yazısında ayrıntılı anlatılıyor.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Rust öğrenme rehberi · Rust veri tipleri · Ownership · Pattern söz dizimi · Arc ve Mutex · E0277 ve E0308 tip hataları · Rust nedir