Rust'ta fonksiyon fn anahtar kelimesiyle tanımlanır; her parametrenin tipi yazılmak zorundadır, dönüş tipi -> işaretinden sonra belirtilir ve fonksiyonun döndürdüğü değer, gövdedeki son ifadenin değeridir. Bunun işleyişini anlamanın anahtarı deyim (statement) ile ifade (expression) arasındaki farktır: ifade bir değer üretir, deyim üretmez ve bir ifadenin sonuna noktalı virgül koymak onu deyime çevirir.

Bu yazıda fonksiyon tanımlamayı, parametreleri, dönüş değerlerini, erken return'ü, blokların değer üretmesini, birden çok değer döndürmeyi, hiç dönmeyen fonksiyonları ve Rust'taki yorum türlerini Rust 1.99.1 ile derlenmiş örneklerle anlatıyoruz. Değişken ve tip kavramlarına henüz hâkim değilseniz önce değişkenler ve veri tipleri yazılarını okuyun; serinin bütünü için Rust öğrenme rehberi sayfasına bakın.

fn ile fonksiyon tanımlamak

fn ile fonksiyon tanımlamak

Fonksiyon tanımı fn, fonksiyon adı, parantezler ve süslü parantez içindeki gövdeden oluşur. Rust'ta fonksiyon ve değişken adları geleneksel olarak snake_case yazılır: bütün harfler küçük, kelimeler alt çizgiyle ayrılır. Fonksiyonu adı ve parantezlerle çağırırsınız. Derleyici fonksiyonun kaynak dosyada nerede tanımlandığına bakmaz; çağıranın görebileceği bir kapsamda tanımlı olması yeterlidir. Bu yüzden aşağıdaki örnekte main'den sonra tanımlanan fonksiyonlar sorunsuz çağrılır:

rust
fn main() {
    println!("Merhaba!");
    baska_fonksiyon();
    olcum_yaz(5, 'h');
}

fn baska_fonksiyon() {
    println!("Başka bir fonksiyon.");
}

fn olcum_yaz(deger: i32, birim: char) {
    println!("Ölçüm: {deger}{birim}");
}
// çıktı: Merhaba!
// çıktı: Başka bir fonksiyon.
// çıktı: Ölçüm: 5h

Satırlar main içinde yazıldıkları sırayla çalışır. main fonksiyonu özeldir: çalıştırılabilir programların giriş noktasıdır. Bir fonksiyonu başka bir fonksiyonun içinde de tanımlayabilirsiniz; bu durumda yalnızca o fonksiyonun içinden görünür. Fonksiyonlar bir değer olarak da kullanılabilir: Reference'a göre bir fonksiyona isimle başvurmak, çağrıldığında o fonksiyonu doğrudan çağıran sıfır boyutlu bir değer üretir. Bu yüzden let f = on_kat; yazıp f(4) diye çağırabilirsiniz; aşağıdaki bölümlerde bunun örneğini göreceksiniz.

Parametreler ve zorunlu tip açıklamaları

Parametreler fonksiyon imzasının parçası olan özel değişkenlerdir. Teknik olarak tanımdaki isimlere parametre, çağrıda verilen somut değerlere argüman denir, ama günlük kullanımda iki kelime birbirinin yerine kullanılır. Rust'ta fonksiyon imzasında her parametrenin tipini yazmak zorunludur. The Book bunu bilinçli bir tasarım kararı olarak açıklar: imzada tipler yazılı olduğunda derleyicinin kodun başka yerlerinde sizden tip açıklaması istemesine neredeyse hiç gerek kalmaz ve daha yardımcı hata mesajları verebilir. Tipi unutursanız derleyici iki olası yorum önerir:

rust
fn kare(x) -> i32 {
    x * x
}
// error: expected one of `:`, `@`, or `|`, found `)`
// help: if this is a parameter name, give it a type

Birden çok parametreyi virgülle ayırırsınız. Reference'a göre parametreler reddedilemez (irrefutable) desenlerdir; yani else kolu olmayan bir let'te geçerli olan her desen parametre olarak da yazılabilir. Örneğin bir tuple parametresini doğrudan imzada parçalayabilirsiniz. Parametreler de varsayılan olarak değişmezdir; gövdede değiştirmek isterseniz önüne mut yazarsınız:

rust
fn bol(bolunen: i32, bolen: i32) -> (i32, i32) {
    (bolunen / bolen, bolunen % bolen)
}

fn ilk((deger, _): (i32, i32)) -> i32 {
    deger
}

fn geri_say(mut n: u32) {
    while n > 0 {
        print!("{n} ");
        n -= 1;
    }
    println!("bitti");
}

fn main() {
    let (bolum, kalan) = bol(17, 5);
    println!("{bolum} {kalan}");
    println!("{}", ilk((9, 4)));
    geri_say(3);
}
// çıktı: 3 2
// çıktı: 9
// çıktı: 3 2 1 bitti

bol fonksiyonu iki değeri bir tuple içinde döndürür; çağıran taraf da bu tuple'ı let (bolum, kalan) deseniyle iki değişkene ayırır. Rust'ta birden çok değer döndürmenin en basit yolu budur. Değerler anlam taşıyorsa ve sayıları artıyorsa isimli alanlara sahip bir struct döndürmek daha okunaklıdır. Desenlerin bütün biçimleri için pattern söz dizimi yazısına bakın.

Parametreye değer geçirmenin bir sahiplik boyutu da vardır. i32 gibi kopyalanan tiplerde fonksiyon kendi kopyasını alır. String gibi heap üzerinde veri tutan tipleri değerle geçirdiğinizde ise sahiplik fonksiyona taşınır ve çağıran taraf o değişkeni artık kullanamaz. Sahipliği vermeden değer geçirmek için referans (&String, &str) kullanılır. Bu konu Rust'ın merkezindedir ve ownership ile borrowing yazılarında ayrıntılı anlatılıyor.

Deyim ve ifade farkı

Deyim ve ifade farkı

Fonksiyon gövdeleri, isteğe bağlı olarak bir ifadeyle biten bir dizi deyimden oluşur. Rust ifade tabanlı bir dil olduğu için bu ayrım diğer dillere göre çok daha önemlidir. Deyimler bir eylem gerçekleştirir ve değer döndürmez. İfadeler ise bir değere değerlendirilir. let y = 6; bir deyimdir; fonksiyon tanımları da deyimdir. Buna karşılık 5 + 6 gibi bir işlem, bir fonksiyon çağrısı, bir makro çağrısı ve süslü parantezle açılan yeni bir blok ifadedir.

Deyimler değer döndürmediği için bir let deyimini başka bir değişkene atayamazsınız. C ya da Ruby gibi bazı dillerde atama, atanan değeri döndürür ve x = y = 6 yazılabilir; Rust'ta bu mümkün değildir:

rust
fn main() {
    let x = (let y = 6);
}
// error: expected expression, found `let` statement
// note: only supported directly in conditions of `if` and `while` expressions

Notta belirtildiği gibi let yalnızca if ve while koşullarında doğrudan kullanılabilir (bu if let ve while let biçimleridir). İlginç bir uç durum da şudur: zaten tanımlı bir değişkene yapılan atama (y = 6) bir ifadedir ama değeri her zaman unit, yani () olur. Bu yüzden zincirleme atama Rust'ta beklediğiniz sonucu vermez:

rust
fn main() {
    let mut y = 0;
    let x = (y = 6);
    println!("{x:?} {y}");
}
// çıktı: () 6

Derleyici bu kod için iki uyarı verir: gereksiz parantezler ve y'ye ilk verilen 0 değerinin hiç okunmaması. x'in değeri 6 değil ()'dir. Şimdi bloğun bir ifade olmasına bakalım. Süslü parantezlerle açılan bir blok, son satırındaki ifadenin değerini üretir:

rust
fn main() {
    let y = {
        let x = 3;
        x + 1
    };
    println!("y: {y}");

    let z = {
        let x = 3;
        x + 1;
    };
    println!("z: {z:?}");
}
// çıktı: y: 4
// çıktı: z: ()

İlk blokta x + 1 satırının sonunda noktalı virgül yoktur; bu satır bloğun son ifadesidir ve blok 4 değerini üretir. İkinci blokta noktalı virgül eklendiği için satır bir deyime dönüşmüştür; blok artık bir değer üretmez ve sonuç unit olur. Rust 1.99.1 burada unused_must_use uyarısıyla hesaplanan değerin kullanılmadığını da bildirir. Bu tek karakterlik fark, bir sonraki bölümdeki en yaygın fonksiyon hatasının kaynağıdır.

Dönüş değerleri ve return

Fonksiyonlar çağıran koda değer döndürebilir. Dönüş değerine isim verilmez ama tipi -> işaretinden sonra yazılmalıdır. Fonksiyonun dönüş değeri, gövde bloğundaki son ifadenin değeriyle aynıdır. return anahtar kelimesiyle fonksiyondan erken de dönebilirsiniz, ama fonksiyonların çoğu son ifadeyi örtük olarak döndürür. Reference bunu şöyle açıklar: fonksiyon gövdesi kavramsal olarak, önce argüman desenlerini bağlayan ve sonra gövdenin değerini return eden bir bloğun içine sarılmıştır. Dönüş tipi hiç yazılmamışsa tip unit'tir.

rust
fn bes() -> i32 {
    5
}

fn arti_bir(x: i32) -> i32 {
    x + 1
}

fn mutlak(x: i32) -> i32 {
    if x < 0 {
        return -x;
    }
    x
}

fn main() {
    println!("{}", bes());
    println!("{}", arti_bir(5));
    println!("{} {}", mutlak(-8), mutlak(3));
}
// çıktı: 5
// çıktı: 6
// çıktı: 8 3

bes fonksiyonunun gövdesinde yalnızca 5 vardır ve bu tamamen geçerli bir Rust fonksiyonudur. mutlak fonksiyonu negatif sayılar için return ile erken döner, diğer durumda son ifade olan x'i döndürür. Şimdi arti_bir içindeki son satırın sonuna noktalı virgül koyalım:

rust
fn arti_bir(x: i32) -> i32 {
    x + 1;
}
// error[E0308]: mismatched types
// expected `i32`, found `()`
// implicitly returns `()` as its body has no tail or `return` expression
// help: remove this semicolon to return this value

Asıl mesaj mismatched types'tır. İmza i32 döndüreceğini söylüyor ama gövdede bir son ifade yok; deyimler değer üretmediği için gövde unit üretiyor ve bu, imzayla çelişiyor. Derleyici çözümü de söylüyor: noktalı virgülü kaldırın. Bu hata Rust'a yeni başlayanların en sık karşılaştığı hatalardan biridir ve mesajı bir kez okumayı öğrendiğinizde düzeltmesi saniyeler sürer.

Hiç dönmeyen fonksiyonlar ve fonksiyon değerleri

Reference'a göre ! (never) tipi hiçbir değeri olmayan, hiç tamamlanmayan hesaplamaları temsil eden bir tiptir ve şimdilik yalnızca fonksiyon dönüş tiplerinde, hiç geri dönmeyen (diverging) fonksiyonları belirtmek için yazılabilir. Her zaman panic! eden ya da programı sonlandıran bir fonksiyon bu tipi kullanabilir. ! tipindeki bir ifade her tipe uyduğu için match kollarında başka tiplerle birlikte kullanılabilir:

rust
fn hata_ver(mesaj: &str) -> ! {
    panic!("{mesaj}");
}

fn main() {
    fn on_kat(x: i32) -> i32 {
        x * 10
    }
    let f = on_kat;
    println!("{}", f(4));

    let sayi: i32 = match "42".parse() {
        Ok(n) => n,
        Err(_) => hata_ver("sayı değil"),
    };
    println!("{sayi}");

    let bos = selam_ver();
    println!("{bos:?}");
}

fn selam_ver() {
    println!("selam");
}
// çıktı: 40
// çıktı: 42
// çıktı: selam
// çıktı: ()

Örnekte üç şey görüyorsunuz. on_kat fonksiyonu main'in içinde tanımlanmış ve bir değişkene atanıp çağrılmıştır. match'in Err kolu ! döndürdüğü için Ok kolundaki i32 ile çakışmaz. Son olarak dönüş tipi yazılmayan selam_ver fonksiyonunun sonucu () değeridir. match ve Result ayrıntıları için match ve if let ile Result ve hata yönetimi yazılarına bakın. İsimsiz fonksiyonlar, yani closure'lar ise ortamdaki değişkenleri yakalayabilir ve closure yazısında anlatılıyor.

Yorumlar

Derleyicinin yok saydığı ama kodu okuyanlara yardım eden açıklamalara yorum denir. Rust'ta yaygın stil, iki eğik çizgiyle başlayıp satır sonuna kadar süren satır yorumudur; birden çok satırlık yorumlar için her satıra // yazılır. Yorumlar kodla aynı satırın sonuna da yazılabilir, ama The Book daha çok açıkladığı kodun üstündeki ayrı bir satırda kullanıldığını belirtir. Reference'a göre /* ... */ blok yorumları da vardır ve iç içe yazılabilir. Üç eğik çizgiyle başlayan /// ve //! ise belge yorumlarıdır; /// ardından gelen öğeyi, //! içinde bulunduğu öğeyi (örneğin dosyanın kendisini) belgeler:

rust
//! Bu dosya yorum türlerini gösteren küçük bir örnektir.

/// İki sayının toplamını döndürür.
fn topla(a: i32, b: i32) -> i32 {
    a + b // satır sonu yorumu
}

fn main() {
    /* blok yorum
       /* iç içe blok yorum da geçerlidir */
       buraya kadar hepsi yok sayılır */
    // let kullanilmaz = 1;
    println!("{}", topla(2, 3));
}
// çıktı: 5

Belge yorumları rustdoc tarafından HTML belgelere dönüştürülür ve içlerindeki kod örnekleri test olarak çalıştırılabilir. Bu konu rustdoc ve dokümantasyon yorumları yazısında ayrıntılı işleniyor.

Küçük bir uygulama: sıcaklık ve Fibonacci

The Book, 3. bölümün sonunda alıştırma olarak sıcaklık dönüştürme ve n'inci Fibonacci sayısını hesaplama gibi küçük programlar önerir. Aşağıdaki örnek bu bölümde öğrendiklerinizi bir araya getirir: tip açıklamalı parametreler, dönüş tipi, son ifadeyle dönüş, mut yerel değişkenler ve bir for döngüsü:

rust
fn santigrat_fahrenhayt(c: f64) -> f64 {
    c * 9.0 / 5.0 + 32.0
}

fn fibonacci(n: u32) -> u64 {
    let mut onceki = 0;
    let mut simdiki = 1;
    for _ in 0..n {
        let sonraki = onceki + simdiki;
        onceki = simdiki;
        simdiki = sonraki;
    }
    onceki
}

fn main() {
    println!("{}", santigrat_fahrenhayt(36.6));
    println!("{}", fibonacci(10));
    println!("{}", fibonacci(50));
}
// çıktı: 97.88000000000001
// çıktı: 55
// çıktı: 12586269025

İlk çıktıdaki uzun ondalık kısım bir hata değil, kayan noktalı sayıların ikilik tabandaki temsilinin doğal sonucudur. fibonacci fonksiyonunun dönüş tipi u64 olduğu için yerel değişkenlerin tipi de derleyici tarafından u64 olarak çıkarılır; i32 seçseydiniz 50'nci Fibonacci sayısı sığmaz ve debug derlemede taşma panic'i alırdınız. for _ in 0..n satırındaki _, döngü değişkenini kullanmayacağımızı söyler; döngülerin ayrıntıları kontrol akışı yazısında.

Sık yapılan hatalar

Sık yapılan hatalar: Son ifadeye noktalı virgül koymak, Parametre tipini yazmamak, Dönüş tipini unutmak, Zincirleme atama yazmak
  • Son ifadeye noktalı virgül koymak: Fonksiyon unit döndürür ve E0308 mismatched types alırsınız; derleyicinin önerdiği gibi noktalı virgülü kaldırın.
  • Parametre tipini yazmamak: Rust fonksiyon imzalarında tip çıkarımı yapmaz; her parametrenin tipi zorunludur.
  • Dönüş tipini unutmak: Tip yazılmazsa fonksiyon unit döndürür; son ifadeniz bir değer üretiyorsa tip uyuşmazlığı hatası alırsınız.
  • Zincirleme atama yazmak: x = y = 6 kalıbı Rust'ta çalışmaz, çünkü atamanın değeri ()'dir.
  • Fonksiyon adlarında camelCase: Derleyici snake_case olmayan fonksiyon adları için uyarı verir; Rust geleneği snake_case'tir.
  • Sahipliği farkında olmadan vermek: String parametreyi değerle almak, çağıranın değişkenini kullanılamaz yapar; yalnızca okumanız gerekiyorsa &str alın.

Sık Sorulan Sorular

return mü son ifade mi kullanmalıyım?

Fonksiyonun sonunda dönüyorsanız son ifade Rust'ta yaygın ve okunaklı olan stildir. return'ü, fonksiyonun ortasında bir koşul sağlandığında erken dönmek için kullanın.

Rust varsayılan parametre değerlerini destekler mi?

Reference'taki fonksiyon söz diziminde parametreler için varsayılan değer yazımı yoktur; her çağrıda bütün argümanları vermeniz gerekir. Benzer bir etki için farklı isimli fonksiyonlar ya da alanları önceden doldurulmuş bir yapılandırma struct'ı kullanılabilir.

Fonksiyon aşırı yükleme (overloading) var mı?

Hayır. Aynı kapsamda aynı isimde, farklı parametre sayısına sahip iki fonksiyon tanımlamayı denediğimizde Rust 1.99.1 error[E0428]: the name `topla` is defined multiple times hatasını verdi. Farklı tiplerle çalışan tek bir fonksiyon için generic ve trait kullanılır; farklı sayıda argüman için ise ayrı isimli fonksiyonlar tanımlanır.

Metot ile fonksiyon arasındaki fark nedir?

Metotlar bir impl bloğunun içinde tanımlanan ve ilk parametresi self olan fonksiyonlardır; nesne.metot() biçiminde çağrılırlar. Ayrıntılar struct ve metotlar yazısında.

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 kontrol akışı · Struct ve metotlar · Closure · Ownership · rustdoc · Python nedir