Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Veri Türleri

Rust’taki her değer, Rust’a ne tür bir veri belirtildiğini söyleyen belirli bir veri türüne (data type) sahiptir, böylece o veriyle nasıl çalışacağını bilir. İki veri türü alt kümesini inceleyeceğiz: skaler (scalar) ve bileşik (compound).

Rust’ın statik tipli (statically typed) bir dil olduğunu aklınızda bulundurun; bu, derleme zamanında tüm değişkenlerin türlerini bilmesi gerektiği anlamına gelir. Derleyici genellikle değere ve onu nasıl kullandığımıza bağlı olarak hangi türü kullanmak istediğimizi çıkarabilir. Bölüm 2’deki “Tahmini Gizli Sayıyla Karşılaştırmak” kısmında parse kullanarak bir String’i sayısal bir türe dönüştürdüğümüz zaman olduğu gibi birçok türün mümkün olduğu durumlarda, şu şekilde bir tür bildirimi eklemeliyiz:

#![allow(unused)]
fn main() {
let tahmin: u32 = "42".parse().expect("Bir sayı değil!");
}

Önceki kodda gösterilen : u32 tür bildirimini eklemezsek, Rust aşağıdaki hatayı görüntüler; bu da derleyicinin hangi türü kullanmak istediğimizi bilmek için bizden daha fazla bilgiye ihtiyacı olduğu anlamına gelir:

$ cargo build
   Compiling no_type_annotations v0.1.0 ($PROJE/listings/ch03-common-programming-concepts/output-only-01-no-type-annotations)
error[E0284]: type annotations needed
 --> src/main.rs:2:9
  |
2 |     let tahmin = "42".parse().expect("Bir sayı değil!");
  |         ^^^^^^        ----- type must be known at this point
  |
  = note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `tahmin` an explicit type
  |
2 |     let tahmin: /* Type */ = "42".parse().expect("Bir sayı değil!");
  |               ++++++++++++

For more information about this error, try `rustc --explain E0284`.
error: could not compile `no_type_annotations` (bin "no_type_annotations") due to 1 previous error

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

Skaler Türler (Scalar Types)

Bir skaler tür, tek bir değeri temsil eder. Rust’ın dört ana skaler türü vardır: tam sayılar (integers), kayan noktalı sayılar (floating-point numbers), Boolean’lar ve karakterler. Bunları diğer programlama dillerinden tanıyabilirsiniz. Rust’ta nasıl çalıştıklarına bir göz atalım.

Tam Sayı Türleri (Integer Types)

Bir tam sayı, kesirli bileşeni olmayan bir sayıdır. Bölüm 2’de bir tam sayı türü kullandık: u32 türü. Bu tür bildirimi, ilişkilendirildiği değerin 32 bitlik alan kaplayan işaretsiz (unsigned) bir tam sayı olması gerektiğini gösterir (işaretli (signed) tam sayı türleri u yerine i ile başlar). Tablo 3-1, Rust’taki yerleşik tam sayı türlerini gösterir. Bir tam sayı değerinin türünü bildirmek için bu varyantlardan (variants) herhangi birini kullanabiliriz.

Tablo 3-1: Rust’taki Tam Sayı Türleri

Uzunlukİşaretliİşaretsiz
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
Mimariye bağlıisizeusize

Her varyant işaretli veya işaretsiz olabilir ve açık bir boyuta sahiptir. İşaretli ve işaretsiz terimleri, sayının negatif olmasının mümkün olup olmadığını ifade eder; diğer bir deyişle, sayının bir işaret alması gerekip gerekmediğini (işaretli) veya sadece pozitif olup bu nedenle işaretsiz olarak temsil edilip edilemeyeceğini (işaretsiz) belirtir. Tıpkı kağıt üzerine sayı yazmak gibidir: İşaret önemli olduğunda bir sayı artı veya eksi işaretiyle gösterilir; ancak sayının pozitif olduğunu varsaymak güvenliyse, herhangi bir işaret olmadan gösterilir. İşaretli sayılar ikiye tümleyen (two’s complement) temsili kullanılarak saklanır.

Her işaretli varyant −(2n − 1) ile 2n − 1 − 1 (dahil) arasında sayılar saklayabilir; buradaki n, o varyantın kullandığı bit sayısıdır. Dolayısıyla, bir i8, −(27) ile 27 − 1 arasında sayılar saklayabilir, bu da -128 ile 127 arasına eşittir. İşaretsiz varyantlar ise 0 ile 2n − 1 arasında sayılar saklayabilir, yani bir u8, 0 ile 28 − 1 arasında sayılar saklayabilir ki bu da 0 ile 255 arasına eşittir.

Ek olarak, isize ve usize türleri, programınızın çalıştığı bilgisayarın mimarisine bağlıdır: 64 bitlik bir mimarideyseniz 64 bit ve 32 bitlik bir mimarideyseniz 32 bittir.

Tam sayı sabitlerini (literals) Tablo 3-2’de gösterilen formlardan herhangi birinde yazabilirsiniz. Birden fazla sayısal tür olabilen sayı sabitlerinin, türü belirlemek için 57u8 gibi bir tür son ekine (suffix) izin verdiğini unutmayın. Sayı sabitleri, sayıyı okumayı kolaylaştırmak için görsel bir ayırıcı olarak _ kullanabilir; örneğin 1_000, 1000 belirtmişsiniz gibi aynı değere sahip olacaktır.

Tablo 3-2: Rust’taki Tam Sayı Sabitleri

Sayı sabitleriÖrnek
Onluk (Decimal)98_222
Onaltılık (Hex)0xff
Sekizlik (Octal)0o77
İkilik (Binary)0b1111_0000
Bayt (sadece u8)b'A'

Peki hangi tür tam sayıyı kullanacağınızı nasıl bileceksiniz? Eğer emin değilseniz, Rust’ın varsayılanları genellikle başlamak için iyi yerlerdir: Tam sayı türleri varsayılan olarak i32’dir. isize veya usize kullanacağınız ana durum, bir çeşit koleksiyonu indekslediğiniz zamandır.

Tam Sayı Taşması (Integer Overflow)

Diyelim ki 0 ile 255 arasında değerler alabilen u8 türünde bir değişkeniniz var. Değişkeni bu aralığın dışındaki bir değere (örneğin 256’ya) değiştirmeye çalışırsanız, tam sayı taşması (integer overflow) meydana gelir ve bu da iki davranıştan biriyle sonuçlanabilir. Hata ayıklama modunda derleme yaptığınızda Rust, tam sayı taşması için kontroller içerir ve bu davranış gerçekleştiğinde programınızın çalışma zamanında panik yapmasına neden olur. Rust, bir program bir hatayla çıktığında (exits) panikleme (panicking) terimini kullanır; panikleri Bölüm 9’daki panic! ile Kurtarılamaz Hatalar” kısmında daha derinlemesine tartışacağız.

--release bayrağıyla yayın (release) modunda derleme yaptığınızda, Rust paniğe neden olan tam sayı taşması kontrollerini içermez. Bunun yerine, taşma meydana gelirse Rust ikiye tümleyerek sarma (two’s complement wrapping) işlemi gerçekleştirir. Kısacası, türün alabileceği maksimum değerden daha büyük değerler, türün alabileceği değerlerin minimumuna “sarılır” (wrap around). Bir u8 durumunda, 256 değeri 0 olur, 257 değeri 1 olur ve bu böyle devam eder. Program panik yapmaz, ancak değişkenin muhtemelen olmasını beklediğiniz gibi olmayan bir değeri olur. Tam sayı taşmasının sarma davranışına güvenmek bir hata olarak kabul edilir.

Taşma olasılığını açıkça (explicitly) yönetmek için, ilkel sayısal türler için standart kütüphane tarafından sağlanan şu metot ailelerini kullanabilirsiniz:

  • wrapping_add gibi wrapping_* metotlarıyla tüm modlarda sarma.
  • Taşma varsa checked_* metotlarıyla None değerini döndürme.
  • overflowing_* metotlarıyla değeri ve taşma olup olmadığını gösteren bir Boolean döndürme.
  • saturating_* metotlarıyla değerin minimum veya maksimum değerlerinde doygunluğa ulaşma (saturate).

Kayan Noktalı (Floating-Point) Türler

Rust ayrıca ondalık noktaları olan kayan noktalı sayılar için iki ilkel (primitive) türe sahiptir. Rust’ın kayan noktalı türleri, sırasıyla 32 bit ve 64 bit boyutunda olan f32 ve f64’tür. Varsayılan tür f64’tür çünkü modern CPU’larda kabaca f32 ile aynı hızdadır ancak daha fazla hassasiyete (precision) sahiptir. Tüm kayan noktalı türler işaretlidir (signed).

İşte kayan noktalı sayıları iş başında gösteren bir örnek:

Dosya adı: src/main.rs

fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}

Kayan noktalı sayılar IEEE-754 standardına göre temsil edilir.

Sayısal İşlemler

Rust, tüm sayı türleri için beklediğiniz temel matematiksel işlemleri destekler: toplama, çıkarma, çarpma, bölme ve kalan (remainder). Tam sayı bölmesi (integer division) en yakın tam sayıya doğru (sıfıra doğru) keser (truncate). Aşağıdaki kod, let ifadesinde her bir sayısal işlemi nasıl kullanacağınızı gösterir:

Dosya adı: src/main.rs

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

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

    // çarpma
    let carpim = 4 * 30;

    // bölme
    let bolum = 56.7 / 32.2;
    let kesilmis = -5 / 3; // Results in -1

    // kalan
    let kalan = 43 % 5;
}

Bu ifadelerdeki (statements) her bir ifade matematiksel bir operatör kullanır ve tek bir değere dönüşür (evaluates), bu değer daha sonra bir değişkene bağlanır. Ek B, Rust’ın sağladığı tüm operatörlerin bir listesini içerir.

Boolean Türü

Çoğu diğer programlama dilinde olduğu gibi, Rust’taki Boolean türünün iki olası değeri vardır: true ve false. Boolean’ların boyutu bir bayttır. Rust’taki Boolean türü bool kullanılarak belirtilir. Örneğin:

Dosya adı: src/main.rs

fn main() {
    let t = true;

    let f: bool = false; // with explicit type annotation
}

Boolean değerlerini kullanmanın ana yolu if ifadesi gibi koşullu yapılardır (conditionals). if ifadelerinin Rust’ta nasıl çalıştığını “Kontrol Akışı” bölümünde ele alacağız.

Karakter (Character) Türü

Rust’ın char türü, dilin en temel alfabetik türüdür. İşte char değerlerini bildirmeye ilişkin bazı örnekler:

Dosya adı: src/main.rs

fn main() {
    let c = 'z';
    let z: char = 'ℤ'; // with explicit type annotation
    let kalp_gozlu_kedi = '😻';
}

Çift tırnak kullanan metin sabitlerinin (string literals) aksine, char sabitlerini tek tırnakla belirttiğimize dikkat edin. Rust’ın char türü 4 bayt boyutundadır ve bir Unicode skaler değerini temsil eder; bu da onun sadece ASCII’den çok daha fazlasını temsil edebileceği anlamına gelir. Aksanlı harfler; Çince, Japonca ve Korece karakterler; emojiler; ve sıfır genişlikli boşluklar (zero-width spaces) Rust’ta geçerli char değerleridir. Unicode skaler değerleri U+0000 ile U+D7FF ve U+E000 ile U+10FFFF (dahil) arasında değişir. Ancak, “karakter” aslında Unicode’da tam olarak karşılığı olan bir kavram değildir, dolayısıyla bir “karakterin” ne olduğuna dair insani sezginiz (intuition) Rust’taki bir char ile örtüşmeyebilir. Bu konuyu Bölüm 8’deki “Metinleri (Strings) Kullanarak UTF-8 Kodlu Metinleri Saklamak” kısmında ayrıntılı olarak tartışacağız.

Bileşik Türler (Compound Types)

Bileşik türler birden fazla değeri tek bir türde gruplayabilir. Rust’ın iki ilkel bileşik türü vardır: tuple’lar ve array’ler (diziler).

Tuple Türü

Bir tuple, çeşitli türlere sahip bir dizi değeri tek bir bileşik türde (compound type) bir arada gruplamanın genel bir yoludur. Tuple’ların sabit bir uzunluğu vardır: Bir kez bildirildiklerinde boyutları büyüyemez veya küçülemez.

Değerlerin virgülle ayrılmış bir listesini parantez içine yazarak bir tuple oluştururuz. Tuple’daki her pozisyonun (konum) bir türü vardır ve tuple’daki farklı değerlerin türlerinin aynı olması gerekmez. Bu örnekte isteğe bağlı tür bildirimleri ekledik:

Dosya adı: src/main.rs

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

Bir tuple tek bir bileşik eleman olarak kabul edildiği için tup değişkeni tüm tuple’a bağlanır (binds). Bireysel değerleri tuple’dan çıkarmak için, bir tuple değerini parçalamak (destructure) amacıyla desen eşleştirmeyi şu şekilde kullanabiliriz:

Dosya adı: src/main.rs

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

    let (x, y, z) = tup;

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

Bu program önce bir tuple oluşturur ve onu tup değişkenine bağlar. Daha sonra tup’ı alıp onu üç ayrı değişkene (x, y ve z) dönüştürmek için let ile birlikte bir desen kullanır. Tek bir tuple’ı üç parçaya böldüğü için buna parçalama (destructuring) denir. Son olarak program, 6.4 olan y değerini yazdırır.

Bir tuple elemanına doğrudan bir nokta (.) ve ardından erişmek istediğimiz değerin indeksini (index) kullanarak da erişebiliriz. Örneğin:

Dosya adı: src/main.rs

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

    let bes_yuz = x.0;

    let alti_nokta_dort = x.1;

    let bir = x.2;
}

Bu program x tuple’ını oluşturur ve daha sonra kendi indekslerini kullanarak tuple’ın her bir elemanına erişir. Çoğu programlama dilinde olduğu gibi, tuple’daki ilk indeks 0’dır.

Hiçbir değere sahip olmayan tuple’ın özel bir adı vardır, birim (unit). Bu değer ve onun karşılık gelen türü de () olarak yazılır ve boş bir değeri veya boş bir dönüş türünü temsil eder. İfadeler (expressions), başka bir değer döndürmezlerse kapalı olarak (implicitly) birim değerini döndürürler.

Array (Dizi) Türü

Birden fazla değere sahip bir koleksiyon oluşturmanın başka bir yolu da array (dizi) kullanmaktır. Bir tuple’dan farklı olarak, bir array’in her elemanı aynı türde olmalıdır. Diğer bazı dillerdeki array’lerin aksine, Rust’taki array’lerin sabit bir uzunluğu vardır.

Bir array’deki değerleri köşeli parantezler içinde virgülle ayrılmış bir liste olarak yazarız:

Dosya adı: src/main.rs

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

Array’ler, verilerinizin şimdiye kadar gördüğümüz diğer türlerdeki gibi heap (yığın) alanı yerine stack (yığıt) üzerinde tahsis edilmesini (allocated) istediğinizde (Bölüm 4’te stack ve heap’i daha ayrıntılı tartışacağız) veya her zaman sabit sayıda elemana sahip olduğunuzdan emin olmak istediğinizde kullanışlıdır. Ancak bir array, bir vektör (vector) türü kadar esnek değildir. Bir vektör, standart kütüphane tarafından sağlanan ve boyutu büyüyebilen veya küçülebilen benzer bir koleksiyon türüdür çünkü içeriği heap üzerinde yaşar. Array mi yoksa vektör mü kullanacağınızdan emin değilseniz, büyük olasılıkla bir vektör kullanmalısınız. Bölüm 8 vektörleri daha detaylı tartışır.

Bununla birlikte, eleman sayısının değişmeyeceğini bildiğiniz durumlarda array’ler daha kullanışlıdır. Örneğin, bir programda ayların isimlerini kullanıyor olsaydınız, her zaman 12 eleman içereceğini bildiğiniz için muhtemelen bir vektör yerine bir array kullanırdınız:

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

Bir array’in türünü yazarken köşeli parantezler içinde her bir elemanın türünü, ardından noktalı virgül ve dizideki eleman sayısını şu şekilde yazarsınız:

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

Burada, her elemanın türü i32’dir. Noktalı virgülden sonra gelen 5 sayısı, dizinin beş eleman içerdiğini belirtir.

Her bir eleman için aynı değere sahip bir array’i şu şekilde başlatabilirsiniz: önce başlangıç değerini, ardından bir noktalı virgülü ve ardından köşeli parantezler içindeki dizinin uzunluğunu belirtebilirsiniz:

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

a adındaki dizi, başlangıçta tamamı 3 değerine ayarlanacak olan 5 eleman içerecektir. Bu, let a = [3, 3, 3, 3, 3]; yazmakla aynı şeydir ancak daha kısa bir yoldur.

Array Elemanlarına Erişim

Bir array, stack üzerinde tahsis edilebilen bilinen, sabit boyutlu tek bir bellek yığınıdır (chunk of memory). İndekslemeyi kullanarak array elemanlarına şu şekilde erişebilirsiniz:

Dosya adı: src/main.rs

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

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

Bu örnekte, ilk adındaki değişken dizideki [0] indeksindeki değer olduğu için 1 değerini alacaktır. ikinci adındaki değişken dizideki [1] indeksinden 2 değerini alacaktır.

Geçersiz Array Elemanlarına Erişim (Invalid Array Element Access)

Bir array’in sonundan daha ileri bir noktasındaki (past the end of the array) bir elemanına erişmeye çalışırsanız ne olacağına bakalım. Kullanıcıdan bir array indeksi almak için Bölüm 2’deki tahmin oyununa benzer şekilde bu kodu çalıştırdığınızı varsayalım:

Dosya adı: src/main.rs

use std::io;

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

    println!("Lütfen bir dizi indeksi girin.");

    let mut indeks = String::new();

    io::stdin()
        .read_line(&mut indeks)
        .expect("Satırı okuyamadı!");

    let indeks: usize = indeks
        .trim()
        .parse()
        .expect("Girilen indeks bir sayi değildi!");

    let element = a[indeks];

    println!("{indeks} indeksindeki elemanın değeri: {element}");
}

Bu kod başarıyla derlenir. Bu kodu cargo run komutunu kullanarak çalıştırırsanız ve 0, 1, 2, 3 veya 4 değerlerinden birini girerseniz, program array’deki o indekse karşılık gelen değeri yazdıracaktır. Eğer dizinin sonunu geçen bir sayı (örneğin 10) girerseniz, şöyle bir çıktı göreceksiniz:

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

Program, indeksleme işleminde geçersiz bir değer kullanıldığı noktada bir çalışma zamanı hatası verdi. Program bir hata mesajıyla kapandı ve son println! ifadesini çalıştırmadı. İndekslemeyi kullanarak bir elemana erişmeye çalıştığınızda Rust, belirttiğiniz indeksin dizi uzunluğundan daha az olup olmadığını kontrol edecektir. İndeks dizi uzunluğundan büyük veya ona eşitse, Rust paniğe neden olur. Bu kontrolün çalışma zamanında gerçekleşmesi gerekir, özellikle de bu durumda, çünkü derleyicinin bir kullanıcının kodu çalıştırdığında ne değer gireceğini bilmesi imkansızdır.

Bu, Rust’ın bellek güvenliği prensiplerinin iş başındaki bir örneğidir. Birçok alt seviye dilde bu tür bir kontrol yapılmaz ve yanlış bir indeks sağladığınızda geçersiz belleğe erişilebilir. Rust, bellek erişimine izin verip devam etmek yerine hemen programdan çıkarak sizi bu tür hatalara karşı korur. Bölüm 9, Rust’ın hata yönetimini ve panik yaratmayan ya da geçersiz bellek erişimine izin vermeyen okunabilir, güvenli kodu nasıl yazabileceğinizi daha ayrıntılı tartışacaktır.