---
title: 'Yabancı Fonksiyon Arayüzü'
source: 'https://academia.sh/tr/kurslar/rust-ileri/yabanci-fonksiyon-arayuzu'
course: 'Rust Ekosistemi ve İleri Konular'
language: tr
updated: '2026-09-13T05:09:27+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'extern "C" bildirimiyle çağrılan bir işlevin gövdesini derleyicinin hiç görmediğini, yalnız bildirilen imzaya güvendiğini açıklar'
    - "İki yabancı çağrının sonucunu Rust'ın kendi karşılığıyla karşılaştırarak yalnızca imzaya güvenmenin doğru sonuç ürettiğini kanıtlar"
    - 'Yanlış yazılmış bir bildirimi derleyicinin fark edemeyeceğini, doğruluğun yazanın sorumluluğunda olduğunu örnekle açıklar'
---

# Yabancı Fonksiyon Arayüzü

Derleyicinin gövdesini hiç görmediği bir C işlevinin, yalnız bildirilen imzasına güvenilerek çağrılması; iki yabancı çağrının sonucunun Rust'ın kendi karşılığıyla eşleştiği koşturularak gösterilir ve bildirimin doğruluğunun yazanın sorumluluğunda olduğu açıklanır.

Önceki ders, güvencenin `unsafe` bir işlevin **imzasına** devredildiğini gösterdi:
`ikinci_ogeyi_al` işlevinin gövdesi Rust'la yazılmıştı, derleyici o gövdeyi okuyabilirdi,
yalnızca ön koşulu kendiliğinden sınamıyordu. Bu ders aynı devri bir adım öteye taşıyor:
gövdesi Rust'la yazılmamış, derleyicinin **hiç görmediği** bir işlevi çağırmak. Orada
sözleşme artık bir ön koşul cümlesi değil, yalnızca bir imzadır — parametre sayısı,
parametre tipleri, dönüş tipi.

Bu sınırın var olma nedeni pratiktir: bir programın ihtiyaç duyduğu her işlevi kendi
dilinde yeniden yazmak zorunlu değildir. İşletim sisteminin sağladığı çağrılar, uzun süredir
kullanılan sayısal ve metin işleme kitaplıkları, donanıma özgü sürücüler — bunların büyük
bölümü C ile yazılmıştır ve C'nin ikili uyumluluk kuralı, bir dile özgü olmayan ortak bir
sınır görevi görür. Rust bu sınırı **yeniden icat etmez**; C'nin çağrı kuralını doğrudan
konuşabilen bir yazım sunar ve karşı tarafın gövdesine dokunmadan onunla iletişim kurar.

## Yalnızca İmzaya Güvenerek Çağırmak

Bir C kitaplığı işlevini Rust'tan çağırmak için o işlevin kaynağını görmeye, hatta aynı
makinede derlenmiş olmasına bile gerek yoktur — yaygın C işlevleri, işletim sisteminin
kendi standart kitaplığında **zaten bağlanmış** durumdadır. Rust programı yalnız bir
bildirim yazar: işlevin adı, aldığı parametrelerin tipleri, döndürdüğü değerin tipi. Bu
bildirim bir `extern "C"` bloğunun içine konur; `"C"` sözcüğü, çağrının hangi ikili
uyumluluk kuralına (parametrelerin yığıtta ya da yazmaçlarda hangi sırayla taşınacağına)
göre yapılacağını söyler — Rust'ın kendi işlevleri farklı bir kuralla derlenebilir, C
kitaplığıyla konuşmak için bu kuralın açıkça C'ninkine çevrilmesi gerekir.

Bildirim yazıldıktan sonra ek bir derleme adımı ya da bağlama adımı gerekmez: derleyici,
programı her zaman bağladığı sistem kitaplığına bu iki adı da arar ve bulur. Parametre
tipleri de elle uydurulmaz; `std::os::raw` modülü, `c_int` ve `c_char` gibi, C'nin `int`
ve `char` tiplerinin bu platformdaki genişliğine karşılık gelen adlar tanımlar — bunlar
Rust'ın kendi `i32` ya da `u8` tipleriyle aynı bellek düzenini paylaşır, ama adlandırma
bildirimin C tarafıyla eşleştiğini kaynaktan okunur kılar.

Sınır yalnız bu yönde işlemez. Bir Rust işlevi de, C tarafından çağrılabilecek bir imzayla
dışarı açılabilir; bu durumda derleyici o işlevin adını değiştirmeden bırakır ki bağlayıcı
onu C'nin beklediği adla bulabilsin. Bu ders yalnız Rust'tan C'ye giden yönü ölçüyor —
`abs` ve `strlen` her ikisi de sistemin sağladığı, Rust programının çağırdığı taraf. Ters
yön aynı sözleşme kuralına dayanır, yalnızca kaynakta hangi tarafın bildirim yazdığı
değişir.

Bildirimin kendisi ile bildirilen işlevi **çağırmak** aynı statüyü taşımaz. Bir
`extern "C"` bloğu yazmak, önceki derste ölçülen ham işaretçiyi kurmaya benziyor: derleyici
yalnızca bir adı ve bir imzayı kaydeder, henüz karşı tarafa geçmemiştir. Tehlike, o adın
gösterdiği yere gerçekten **atlandığında** başlar — bu yüzden yalnızca çağrı `unsafe` bloğu
ister, bildirimin kendisi istemez.

```rust
// bildirim.rs — bildirimin kendisi unsafe gerektirmiyor, cagirmak gerektiriyor
use std::os::raw::c_int;

extern "C" {
    fn abs(x: c_int) -> c_int;
}

fn main() {
    // Islevi bir gosterge olarak tasimak onu cagirmaz; unsafe gerekmiyor.
    let gosterge: unsafe extern "C" fn(c_int) -> c_int = abs;
    println!("bildirim derlendi, cagrilmadi: {}", gosterge as usize != 0);
}
```

```
bildirim derlendi, cagrilmadi: true
```

Bu koşum `abs`i hiç çağırmadı; yalnızca bildirilen işlevi bir değişkene taşıdı ve o
değişkenin boş olmadığını (sıfır adresi göstermediğini) doğruladı — gerçek adresin kendisi
hiçbir yerde basılmadı. **DS6.** Derleme bu satırda hiçbir uyarı vermedi çünkü bildirilen
işlev gerçekten kullanıldı, yalnızca çağrılmadı; kullanılmayan bir bildirim derleyici
tarafından ölü kod olarak işaretlenirdi. Bildirimin kendisinin `unsafe` istememesi, dilin
"bu isim bir yere işaret ediyor" demekle "o yere şimdi atlıyorum" demek arasında kurduğu
ayrımın bir başka örneğidir — önceki derste ham işaretçi kurmak ile onu okumak arasındaki
ayrımın aynısı.

## İki Yabancı Çağrının Ölçümü

Aşağıdaki koşum iki yabancı işlevi çağırıyor: mutlak değer alan `abs` ve bir bayt dizisinin
uzunluğunu sayan `strlen`. İkisinin de gövdesi bu programın içinde yok — yalnız bildirimleri
var. Sonuçlar, aynı işi yapan Rust karşılıklarıyla karşılaştırılıyor.

**DS7.** Mutlak değeri alınan sayı `-7`; uzunluğu ölçülen dizge `"merhaba"` (yedi bayt,
hiçbir çok baytlı karakter taşımıyor).

```rust
// yabanci.rs — iki yabanci cagrinin sonucu ve rust karsiligi
use std::ffi::CString;
use std::os::raw::{c_char, c_int};

extern "C" {
    fn abs(x: c_int) -> c_int;
    fn strlen(s: *const c_char) -> usize;
}

fn main() {
    let mutlak = unsafe { abs(-7) };
    let metin = CString::new("merhaba").unwrap();
    let uzunluk = unsafe { strlen(metin.as_ptr()) };

    println!("yabanci mutlak: {} (rust karsiligi {})", mutlak, (-7i32).abs());
    println!("yabanci uzunluk: {} (rust karsiligi {})", uzunluk, "merhaba".len());
}
```

```
yabanci mutlak: 7 (rust karsiligi 7)
yabanci uzunluk: 7 (rust karsiligi 7)
```

İki çağrı da `unsafe` bloğunun içinde — ama önceki dersteki `unsafe`den farklı bir
gerekçeyle. Orada tehlike, dilimin sınırlarının dışına taşan bir okumaydı; burada tehlike,
derleyicinin karşı tarafın **hiçbir şeyini** bilmemesidir. `abs` ve `strlen` işlevlerinin
kaynak kodu bu programın hiçbir yerinde yok; derleyici onları görmeden, yalnız yukarıdaki
iki satırlık bildirime güvenerek çağrıyı üretti. **DS8.** Sonuç iki satırda da birebir
eşleşti: yabancı `abs` çağrısı `7` döndürdü, Rust'ın kendi `abs` yöntemi de `7`; yabancı
`strlen` çağrısı `7` döndürdü, Rust'ın `len` yöntemi de `7`. Eşleşme tesadüf değil —
bildirimin C tarafındaki gerçek imzayla birebir örtüşmesinin sonucu.

`CString::new` çağrısı burada bilinçli bir adım: Rust'ın kendi dizgi tipi, sonunda sıfır
baytı bulundurmaz ve uzunluğunu ayrı bir sayaçta tutar; C'nin dizgi kuralı ise sonu sıfır
baytıyla biten bir bayt dizisidir. `strlen`i doğrudan bir Rust dizgesiyle çağırmak, iki
kuralın farklı olduğu bir sınırda tip uyuşmazlığına yol açar; `CString`, aradaki çeviriyi
yaparak sonunda sıfır baytı olan bir bayt dizisi kurar ve yalnız o dizinin başlangıç
adresini `strlen`e geçirir.

## Sınırın İki Tarafındaki Ayrı Kurallar

Bu derste sınırı geçen değerler yalnızca tek tam sayılar ve bayt dizileri; bir yapı
(struct) sınırı geçseydi ayrı bir kural daha devreye girerdi. Rust'ın kendi yapı
düzenleyicisi, alanları derleme sırasında istediği gibi yeniden sıralayabilir — bu,
programın davranışını değiştirmeyen, yalnız bellekteki yerleşimi etkileyen bir
serbestliktir. C tarafı ise bir yapının alanlarını bildirildiği sırayla, hizalama
kurallarına göre art arda yerleştirmeyi **garanti eder**. İki taraf da aynı adları taşıyan
bir yapı üzerinde anlaşsa bile, biri alanları yeniden sıralarken öteki sıralamayı sabit
tutuyorsa, aynı bellek bloğu iki farklı yorumla okunur. Bu ders yapı geçirmiyor, yalnızca
tam sayı ve işaretçi geçiriyor; sınırın bu parçası burada ölçülmüyor, yalnızca var
olduğu kaynaktan anılıyor.

`abs` ve `strlen` seçilme nedeni, ikisinin de belleği yalnızca **okumasıdır** — hiçbiri
yeni bir bellek bloğu ayırıp Rust tarafına bırakmıyor. Bu, FFI sınırındaki en önemli
kısıtlardan birini gösteriyor: bir C işlevi kendi içinde bellek ayırıp bir işaretçi
döndürseydi, o belleği kimin, hangi ayırıcıyla serbest bırakacağı ayrı bir sözleşme
konusu olurdu. Rust'ın kendi büyüyen dizisi ve C'nin `malloc` işlevi aynı ayırıcıyı
kullanmak zorunda değildir; C tarafında ayrılan bir bellek bloğunu Rust'ın kendi bırakma
kuralıyla serbest bırakmaya kalkmak, iki ayrı ayırıcının defterini karıştırır ve bu da
tanımsız davranış üretir. Bu ders bu durumu koşturmuyor — `abs` bir tam sayı, `strlen` bir
uzunluk döndürüyor, ikisi de yeni bellek ayırmıyor — ama sınırın bu kısmının varlığını
kaynaktan görünür kılmak için anılıyor.

Sınırın her parçası `devredildi` ya da `dilin dışında` sınıfına düşmüyor; `CString::new`
çağrısı bunlardan birine ait değil. C'nin sıfır baytıyla biten dizgi kuralı, bir dizginin
içinde sıfır bayt **taşıyamayacağı** anlamına gelir — sıfır bayt görüldüğü an dizgi orada
biter. `CString::new`, verdiği bayt dizisinin içinde bir sıfır bayt olup olmadığını
çalışma zamanında sınar ve varsa çağrıyı `panik` ile değil bir hata değeriyle durdurur.

```rust
// sifir_bayt.rs — CString::new govde icindeki sifir bayti denetliyor
use std::ffi::CString;

fn main() {
    let temiz = CString::new("merhaba");
    let bozuk = CString::new("me\0rhaba");

    println!("temiz dizge kuruldu mu: {}", temiz.is_ok());
    println!("govde ici sifir bayt reddedildi mi: {}", bozuk.is_err());
}
```

```
temiz dizge kuruldu mu: true
govde ici sifir bayt reddedildi mi: true
```

**DS9.** Bu, sınırın `strlen` çağrısındaki devredilmiş kısmından farklı bir yer: burada
denetim derleyicide değil çalışma zamanında yapılıyor, ama yine de **yapılıyor** — `Result`
tipi çağırana hatayı görmezden gelme izni vermiyor. Sınırın tamamı yazana devredilmiş
değildir; C'nin kendi biçim kuralına uymayan bir girdi, daha yabancı çağrıya hiç
ulaşmadan durduruluyor.

Hata bildirme biçimi de sınırın iki tarafında ayrıdır. Rust'ta bir işlemin başarısızlığı
genellikle bir `Result` değeriyle taşınır ve derleyici bu değerin ele alınıp alınmadığını
denetler. C'nin böyle bir tipi yoktur; bir işlevin başarısız olduğu genellikle özel bir
dönüş değeriyle (örneğin negatif bir sayı ya da sıfır) ya da ayrı bir global durumla
işaretlenir. `extern "C"` bildirimi bu farkı ortadan kaldırmaz — bildirilen dönüş tipi
`c_int` ise, o değerin bir hata kodu mu yoksa sıradan bir sonuç mu olduğunu bilmek yine
çağıranın C tarafının belgesini okumasına bağlıdır; derleyici bu ayrımı tipten çıkaramaz,
çünkü tip ikisi için de aynıdır.

Karakter kodlaması, `strlen`in döndürdüğü sayının ne anlama geldiği konusunda ayrı bir
tuzak taşıyor. Rust'ın kendi `len` yöntemi de, `strlen` de aynı şeyi sayar: dizginin
**bayt** uzunluğunu — dizginin görsel olarak kaç karakterden oluştuğunu değil. Bu derste
kullanılan `"merhaba"` yalnızca tek baytlık karakterlerden oluştuğu için bu ayrım
görünmedi; birden çok baytla kodlanan bir karakter taşıyan bir dizgede iki sayı birbirinden
ayrılır.

```rust
// bayt_karakter.rs — bayt uzunlugu ile karakter sayisinin ayrilmasi
use std::ffi::CString;
use std::os::raw::c_char;

extern "C" {
    fn strlen(s: *const c_char) -> usize;
}

fn main() {
    let dizge = "şafak";
    let dizi = CString::new(dizge).unwrap();
    let yabanci_uzunluk = unsafe { strlen(dizi.as_ptr()) };

    println!("rust bayt uzunlugu: {}", dizge.len());
    println!("rust karakter sayisi: {}", dizge.chars().count());
    println!("yabanci strlen: {}", yabanci_uzunluk);
}
```

```
rust bayt uzunlugu: 6
rust karakter sayisi: 5
yabanci strlen: 6
```

**DS10.** `dizge.len()` ile yabancı `strlen` çağrısı burada da birebir eşleşti (`6`) —
ikisi de bayt sayıyor. Ayrı düşen, `chars().count()` yöntemiyle bulunan karakter sayısı
(`5`): dizgedeki `ş` harfi Rust kaynağında tek bir karakter olarak yazılsa da UTF-8
kodlamasında iki bayt tutuyor. `strlen`e güvenen bir C tarafı bu sayıyı her zaman bayt
olarak okur; onu karakter sayısı sanan bir çağıran, tek baytlık olmayan bir dizgede yanlış
bir varsayımla ilerlemiş olur. Derleyici bu yanlış varsayımı da fark edemez — dönüş tipi
`usize`dir ve `usize`in bir bayt sayısı mı yoksa karakter sayısı mı taşıdığını söylemez.

## Sınırlayıcı Ölçüm

Bu iki çağrının doğru çalışması, bildirimin doğru yazılmış olmasına bağlıydı — ve bu
doğruluğu derleyici hiçbir noktada sınamadı. `abs`in gerçek C imzası tek bir tam sayı alıp
tek bir tam sayı döndürür; bildirim bunu doğru yazdığı için çağrı doğru çalıştı. Bildirim
yanlış yazılsaydı — örneğin `abs`in iki parametre aldığı ya da bir kayan noktalı sayı
döndürdüğü bildirilseydi — derleyici bunu **fark edemezdi**. Derleyicinin elinde karşı
tarafın gövdesi yok; yalnızca yazılan bildirime bakıyor ve o bildirime uygun bir çağrı
üretiyor. Yanlış bir bildirimin sonucu bu derste koşturulmuyor, çünkü sonucu bu makineye
özgü ve tanımsız davranış sınıfına girer — yığıttaki değerler yanlış tipte yorumlanabilir,
program yanlış bir sayı üretebilir ya da çökebilir.

Bu, `devredildi` ile `dilin dışında` sınıflarının kesiştiği yerdir. Denetim, önceki
derste olduğu gibi yazana devredildi — ama burada devredilen şey bir ön koşul cümlesi
değil, karşı tarafın **doğruluğuna** dair hiçbir garantisi olmayan bir imzadır. Karşı
tarafın kendisi Rust'ın dışında yazılmış, Rust'ın derleme sürecinin dışında derlenmiş ve
sistem tarafından zaten bağlanmıştır; derleyicinin ona dair tek bilgisi, programcının
yazdığı iki satırdır. Bildirim doğruysa iki dil arasındaki sınır görünmez olur — yukarıdaki
koşum tam olarak bunu gösterdi. Bildirim yanlışsa, hatanın nerede olduğunu derleyici değil,
yalnızca çalışma zamanındaki gözlem ortaya çıkarabilir.

Bu sınırın genişliği, tek bir yanlış bildirimle değil, bildirimin **her alanıyla** ölçülür.
Parametre sayısı yanlışsa, çağrı yığıttan ya da yazmaçlardan okuduğu yerleri kaydırır ve
sonraki her parametre de yanlış bir değer taşır. Parametre tipi doğru genişlikte değilse —
örneğin gerçek C imzası dört baytlık bir tam sayı beklerken bildirim sekiz baytlık bir tam
sayı yazıyorsa — çağıran taraf, karşı tarafın okumadığı fazladan baytları da göndermiş ya
da eksik göndermiş olur. Dönüş tipi yanlışsa, çağrının döndürdüğü baytlar yanlış bir tipe
göre yorumlanır. Üç durumun da ortak noktası, derleyicinin hiçbirini reddetmemesidir:
`extern "C"` bloğu sözdizimsel olarak geçerli bir bildirimdir, C tarafındaki gerçek
imzayla örtüşüp örtüşmediği derlemenin konusu değildir.

## Özet

- `extern "C"` bloğu, gövdesi Rust'ın dışında yazılmış bir işlevi yalnızca imzasıyla
  bildirir; derleyici o gövdeyi hiçbir zaman görmez, güvencesi tamamen **dilin dışında**
  kalır
- C kitaplığının yaygın işlevleri sistemde zaten bağlıdır; bildirim yazıldıktan sonra ek
  bir derleme ya da bağlama adımı gerekmez
- İki yabancı çağrının (`abs`, `strlen`) sonucu, Rust'ın kendi karşılığıyla birebir
  eşleşti — eşleşme, bildirimin gerçek C imzasıyla doğru yazılmış olmasının sonucudur
- `CString`, Rust'ın uzunluk sayaçlı dizgesi ile C'nin sıfır baytıyla biten dizisi
  arasındaki kural farkını, sınırda açık bir dönüşümle kapatır
- Bildirim yanlış yazılsaydı derleyici bunu fark edemezdi; doğruluk tamamen yazanın
  sorumluluğundadır — bu, `devredildi` ile `dilin dışında` sınıflarının kesiştiği nokta
- Sınırın her parçası devredilmiş değildir: `CString::new`, gövde içinde bir sıfır bayt
  olup olmadığını çalışma zamanında sınar ve varsa bir hata değeriyle durdurur — bu
  denetim çalışma zamanında olsa da hâlâ **sürdürüyor**

## Sonraki Adım

Bu derste sınır, iki dil arasındaydı; bildirim doğruysa hangi makinede koştuğunun önemi
yoktu — `abs` ve `strlen` çağrıları, program hangi işlemcide ya da hangi işletim
sisteminde koşarsa koşsun aynı sonucu üretti, çünkü ikisi de sistemin kendi standart
kitaplığına bağlanıyordu. Sıradaki ders bu bağımsızlığı tersine çeviriyor: aynı kaynağın,
koştuğu **hedefe** göre farklı bir gövdeye ihtiyaç duyması. Orada güvence bir bildirime
değil, hedefin kendi verdiği kısıtlara bağlı olacak — ve o kısıt, bu derste olduğu gibi
bir dış kitaplığın imzasında değil, kaynağın kendisinde koşullu olarak yazılacak.
