Fonksiyonlar

Fonksiyonlar Rust kodunda sıklıkla kullanılır. Dildeki en önemli fonksiyonlardan birini zaten gördünüz: birçok programın giriş noktası olan main fonksiyonu. Ayrıca, yeni fonksiyonlar tanımlamanızı sağlayan fn anahtar sözcüğünü de gördünüz.

Rust kodu, tüm harflerin küçük olduğu ve ayrı sözcüklerin altını çizdiği, fonksiyon ve değişken adları için geleneksel stil olan yılan stilini kullanır.

Örnek bir fonksiyon tanımı içeren bir program:

Dosya adı: src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

Rust'ta fn ve ardından bir fonksiyon adı ve bir parantez dizisi girerek bir fonksiyon tanımlarız. Parantezler derleyiciye fonksiyon argüman gövdesinin nerede başlayıp nerede bittiğini söyler.

Tanımladığımız herhangi bir fonksiyonu, adını ve ardından bir parantez dizisini girerek çağırabiliriz. Programda another_function tanımlı olduğundan, main fonksiyonu içinden çağrılabilir. Kaynak kodda ana fonksiyondan sonra another_function'u tanımladığımızı unutmayın; daha önce de tanımlayabilirdik. Rust, fonksiyonlarınızı nerede tanımladığınızla ilgilenmez, yalnızca arayanın görebileceği bir kapsamda bir yerde tanımlanmalarını ister. Kapsam dışı fonksiyonları geleneksel yöntemle çağıramazsınız.

İşlevleri daha fazla keşfetmek için functions adında yeni bir proje başlatalım. other_function örneğini src/main.rs içine atın ve çalıştırın. Aşağıdaki çıktıyı görmelisiniz:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.

Satırlar, ana fonksiyonda göründükleri sırayla yürütülür. İlk olarak, “Hello, world!” ekrana yazdırılır ve ardından another_function fonksiyonu çağrılır ve belirtilen parametrelerle birlikte yürütülür.

Parametreler

Fonksiyonları, bir fonksiyonun yapısının parçası olan özel değişkenler olan parametrelere sahip olacak şekilde tanımlayabiliriz. Bir fonksiyonun parametreleri olduğunda, ona bu parametreler için somut değerler sağlayabilirsiniz. Teknik olarak somut değerlere argümanlar denir, ancak gündelik konuşmalarda insanlar parametre ve argüman kelimelerini bir fonksiyonun tanımındaki değişkenler veya bir fonksiyonu çağırdığınızda iletilen somut değerler için birbirinin yerine kullanma eğilimindedir.

another_function'un bu sürümünde bir parametre ekliyoruz:

Dosya adı: src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

Şimdi bu kodu çalıştırmaya çalışın; şu çıktıyı almalısınız:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/functions`
The value of x is: 5

another_function tanımı, x adında bir parametreye sahiptir. x'in tipi i32 olarak belirtilmiştir. 5'i another_function fonksiyonuna parametre olarak verdiğimizde, println! makrosu, x'i içeren parametre listesinde x yerine 5'i koyar.

Fonksiyon yapılarında, her parametrenin türünü belirtmelisiniz. Bu, Rust'ın tasarımında bilinçli olarak verilmiş bir karardır: fonksiyon tanımlarında tip açıklamalarının zorunluluğu, derleyicinin, ne türü istediğinizi anlamak için kodun başka bir yerinde bu tarz yaygın tür tanımlarını kullanmanıza neredeyse hiç ihtiyaç duymadığı anlamına gelir. Derleyici, fonksiyonun ne tür beklediğini biliyorsa, daha yararlı hata mesajları da verebilir.

Birden çok parametre tanımlarken, parametre bildirimlerini aşağıdaki gibi virgülle ayırın:

Dosya adı: src/main.rs

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Bu örnek, iki parametreli print_labeled_measurement adlı bir fonksiyon oluşturur. İlk parametre value olarak adlandırılmıştır ve türü i32'dir. İkincisi, unit_label olarak adlandırılmıştır ve char türündendir. Fonksiyon hem value hem de unit_label'in değerini içeren metni ekrana yazdırır.

Bu kodu çalıştırmayı deneyelim. functions klasörünüzün src/main.rs dosyasındaki mevcut programı önceki örnekle değiştirin ve cargo run komutunu kullanarak çalıştırın:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/functions`
The measurement is: 5h

Fonksiyonu value'nin değeri 5 ve unit_label'in değeri 'h' olacak şekilde çağırdığımız için program çıktısı bu değerleri içerecektir.

İfade Yapıları ve İfadeler

Fonksiyon gövdeleri, isteğe bağlı olarak ifade yapılarıyla biten bir dizi ifadeden oluşur. Şimdiye kadar ele aldığımız fonksiyonlar bir bitiş ifadesi içermemişti, ancak bir ifade yapısının parçası olarak ifadeler görmüştünüz. Rust ifade tabanlı bir dil olduğundan, bu anlaşılması gereken önemli bir ayrımdır. Diğer diller aynı ayrımlara sahip değildir, o halde şimdi ifade yapılarının ve ifadelerin ne olduğuna ve farklılıklarının fonksiyon gövdelerini nasıl etkilediğine bakalım.

İfade yapıları, bazı eylemleri gerçekleştiren ve bir değer döndürmeyen talimatlardır. İfadeler bir sonuç değeri olarak değerlendirilir. Bazı örneklere bakalım.

Aslında zaten ifade yapılarını ve ifadeleri kullandık. let anahtar sözcüğü ile bir değişken oluşturmak ve ona bir değer atamak bir ifade yapısıdır. Liste 3-1'deki let y = 6; bir ifade yapısıdır.

Dosya adı: src/main.rs

fn main() {
    let y = 6;
}

Liste 3-1: Bir ifade yapısı içeren main fonksiyonu tanımı

Fonksiyon tanımları da ayrıca ifade yapıları olarak değerlendirilir. Önceki örneğin tamamı kendi içinde bir ifade yapısıdır.

İfade yapıları herhangi bir değer döndürmez. Statements do not return values. Buna göre, let ifade yapısıyla başka bir değeri halihazırda tanımlanmış bir değerle eşitleyemezsiniz. Bunu denerseniz, şu hatayı alacaksınızdır:

Dosya adı: src/main.rs

fn main() {
    let x = (let y = 6);
}

Bu programı çalıştırırsanız, şuna benzer bir hata alacaksınızdır:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found statement (`let`)
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: variable declaration using `let` is a statement

error[E0658]: `let` expressions in this position are experimental
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
  = help: you can write `matches!(<expr>, <pattern>)` instead of `let <pattern> = <expr>`

warning: unnecessary parentheses around assigned value
 --> src/main.rs:2:13
  |
2 |     let x = (let y = 6);
  |             ^         ^
  |
  = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
  |
2 -     let x = (let y = 6);
2 +     let x = let y = 6;
  | 

For more information about this error, try `rustc --explain E0658`.
warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` due to 2 previous errors; 1 warning emitted

let y = 6 ifade yapısı bir değer döndürmez, dolayısıyla x'e atanacak bir şey yoktur. Bu, atamanın; atanan değeri döndürdüğü C ve Ruby gibi diğer dillerden farklıdır. Bu dillerde x = y = 6 yazabilir ve hem x hem de y'nin 6 değerine sahip olmasını sağlayabilirsiniz; ancak Rust'ta durum böyle değil.

İfadeler bir değer olarak değerlendirilir ve Rust'ta yazacağınız kodun geri kalanının çoğunu oluşturur. 11 değerini veren bir ifade olan 5 + 6 gibi bir matematik işlemini düşünün. İfadeler ifade yapılarının bir parçası olabilir: Liste 3-1'de, let y = 6 ifade yapısındaki 6; 6 değerini y'ye veren bir ifadedir. Bir fonksiyonu çağırmak bir ifadedir. Makro çağırmak bir ifadedir. Parantezlerle oluşturulan bir kapsam bloğu bir ifadedir, örneğin:

Dosya adı: src/main.rs

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}");
}

Bu ifade:

{
    let x = 3;
    x + 1
}

bu durumda 4 olarak değerlendirilen bir bloktur. Bu değer, let ifade yapısının bir parçası olarak y'ye atanır. Şu ana kadar gördüğünüz çoğu satırın aksine x + 1 satırının sonunda noktalı virgül bulunmadığına dikkat edin. İfadeler en sona noktalı virgül eklenmesini gerektirmez. Bir ifadenin sonuna noktalı virgül eklerseniz, onu bir ifade yapısına dönüştürürsünüz ve o, bir değer döndürmeyecektir. Bir sonraki başlığımız olacak olan fonksiyon dönüş değerlerini ve ifadelerini keşfederken bunu aklınızda bulundurun.

Dönüş Değerleri Olan Fonksiyonlar

Fonksiyonlar, onları çağıran koda değerler döndürebilir. Dönüş değerlerini adlandırmıyoruz, ancak türlerini bir oktan (->) sonra bildirmeliyiz. Rust'ta, fonksiyonun dönüş değeri, bir fonksiyonun gövdesinin bloğundaki son ifadenin değeri ile eş anlamlıdır. return anahtar sözcüğünü kullanarak ve ona bir değer belirterek bir fonksiyondan erken dönebilirsiniz, ancak çoğu fonksiyon son ifadeyi örtük yapıda döndürür. Değer döndüren bir fonksiyon örneği:

Dosya adı: src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

five fonksiyonunda hiçbir fonksiyon çağrısı, makro ve hatta let ifade yapısı yoktur; yalnızca 5 sayısı tek başınadır. Bu, Rust'ta tamamen geçerli bir fonksiyondur. Fonksiyon dönüş türünün de -> i32 olarak belirtildiğine dikkat edin.

Bu kodu çalıştırmayı deneyin -hatasız çalışmalıdır-, çıktı şöyle görünmelidir:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/functions`
The value of x is: 5

five'ın içindeki 5, fonksiyonun dönüş değeridir, bu nedenle fonksiyonun dönüş türü i32'dir. Bunu daha detaylı inceleyelim. İki önemli yer vardır: birincisi, let x = five(); satırı bir değişkeni başlatmak için bir fonksiyonun dönüş değerini kullandığımızı gösterir. five fonksiyonu 5 döndürdüğünden, bu satır aşağıdakiyle tamamen aynıdır:


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

Ayrıca, five fonksiyonunda parametre yoktur ve dönüş değerinin türünü tanımlayan herhangi bir ifade yoktur, ancak işlevin gövdesi, değerini döndürmek istediğimiz bir ifade olduğu için noktalı virgül içermez. Yalnızca 5 yazabiliriz.

Başka bir daha örneğe bakalım:

Dosya adı: src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

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

Bu kodu çalıştırmak bize The value of x is: 6 çıktısını verecektir. Ancak x + 1'i içeren satırın sonuna noktalı virgül koyarsak, onu bir ifadeden bir ifade yapısına çevirmiş oluruz ve bir hata alırız.

Dosya adı: src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

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

Bu kodun derlenmesi aşağıdaki gibi bir hata üretir:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
8 |     x + 1;
  |          - help: consider removing this semicolon

For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` due to previous error

Hata mesajı, “uyumsuz türler“ bu kodla ilgili temel sorunu ortaya koymaktadır. plus_one fonksiyonunun tanımı, bir i32 döndüreceğini söyler, ancak ifade yapısı () ile ifade edilen bir değer olarak değerlendirilmez. İstenilen şey i32 türünde döndürmektir, bir ifade yapısının döndürdüğü gibi ()'i döndürmek değildir. Bu nedenle, fonksiyon tanımıyla çelişen ve bir hatayla sonuçlanan hiçbir şey döndürülmemiş olur. Bu çıktıda Rust, muhtemelen bu sorunun düzeltilmesine yardımcı olacak bir mesaj gösterir: Rust, noktalı virgülün kaldırılmasını önerir, bu da hatayı düzeltir.