Tüm Rust rehberleri
TEKNİK REHBER ARAÇLAR MODÜL 2026

Modüller & Crate'ler
mod, use, görünürlük

mod, use, pub görünürlük, crate kökü ve workspace; büyük projeyi düzenleme — C header/translation unit ve include modeliyle karşılaştırmalı.

00 C #include modelinden Rust'a

C'de modülerlik dilin değil, ön işlemcinin işidir: #include metinsel kopyalama yapar, isim alanı yoktur, tek tanım kuralını (ODR) elle korursunuz. Rust'ta modül sistemi dilin parçasıdır — tek geçişte çözülür, isim alanları nettir.

C/C++ geçmişinizden geleni hatırlayalım. Bir .h dosyası bildirimleri (deklarasyon) taşır, eşlenik .c/.cpp dosyası tanımları (definition). Derleyici her çeviri birimini (translation unit) ayrı görür; #include direktifi hedef başlığın tüm metnini olduğu gibi kaynağın içine kopyalar. Bu modelin bilinen ağrı noktaları:

C/C++ sorunuKaynağıRust'taki durum
Çift include / döngüsel başlıkMetinsel include, koruyucu yokYok — mod bir kez çözülür
Include guard / #pragma onceAynı başlığın tekrar açılmasıGereksiz — modül kimliği derleyicide
ODR ihlali (tek tanım kuralı)Aynı sembolün iki tanımıDerleyici tek modül ağacı tutar
Global isim çakışmasıİsim alanı yok (C)Her item bir modül yoluna ait
Makro sızıntısıÖn işlemci metinde çalışırHijyenik makrolar, kapsam saygılı
Build sırası / forward declTek geçişli derlemeBildirim sırası önemsiz

Kritik fark şudur: Rust'ta mod bir #include değildir. #include "foo.h" dosya içeriğini metinsel olarak yapıştırır; mod foo; ise derleyiciye "bu noktada foo adlı bir modül vardır, içeriğini foo.rs dosyasından oku ve modül ağacına bu isimle yerleştir" der. Sonuç bir isim alanıdır, kopyalanmış metin değil. Bir öğeyi başka modülde kullanmak için içeriği include etmezsiniz; use ile yola atıfta bulunursunuz.

NOT

C/C++'ta sıralama mantığı: önce ileri bildirim, sonra include, en sonda tanım. Rust'ta bu zihinsel yükü bırakın. Bir modülde tanımlanmış fn bar()'ı, ondan önce yazılmış bir fonksiyondan çağırabilirsiniz; derleyici tüm crate'i tek bir öğe ağacı olarak görür ve forward declaration diye bir kavram yoktur.

C: .h (bildirim) + .c (tanım) → #include metinsel kopya → linker sembol çözer
Rust: crate → mod ağacı → use ile yol → tek derleme birimi

Bir başka önemli ayrım: C'de çeviri birimi = dosya, linkleme birimi = nesne dosyaları topluluğudur. Rust'ta derleme birimi = crate'tir (tek bir .rs dosyası değil). Bir crate'in onlarca dosyası olsa da derleyici hepsini tek seferde, tek bir bağlam içinde işler. Bu yüzden modüller arası görünürlük ve isim çözümü tutarlıdır; ayrı ayrı derlenip linkte birleştirilen çeviri birimlerinin "ya başlıkta yanlış imza varsa" sınıfı hataları Rust'ta oluşmaz.

Bu bölümde

  • C'nin header/source ikiliği ve metinsel #include modelinin ağrı noktalarını (guard, ODR, çakışma) hatırladık
  • mod'un bir include olmadığını; isim alanı oluşturduğunu netleştirdik
  • Rust'ta derleme biriminin crate olduğunu, tüm modüllerin tek bağlamda çözüldüğünü gördük
  • Forward declaration ve bildirim sırası kaygısının ortadan kalktığını öğrendik

01 Crate kökü

Crate, Rust'ın derleme ve dağıtım birimidir — C'deki bir kütüphane veya çalıştırılabilir hedefin karşılığı. İki tür vardır: çalıştırılabilir binary crate (main.rs) ve yeniden kullanılabilir library crate (lib.rs).

C'de "proje" bulanık bir kavramdır; gerçek birim, üretilen artefakttır: bir yürütülebilir dosya ya da bir .a/.so kütüphane. Rust'ta bunun adı crate'tir ve net bir kökü vardır. Binary crate'in kökü src/main.rs, library crate'in kökü src/lib.rs'tir. Bu dosya, modül ağacının tepesidir — yani crate'in kök modülüdür. Kök modülün adı her zaman crate anahtar sözcüğüyle ifade edilir.

bash
# Binary crate (yürütülebilir): kök = src/main.rs
cargo new uygulamam

# Library crate (kütüphane): kök = src/lib.rs
cargo new mylib --lib

Bir paket (package) içinde en fazla bir library crate bulunabilir ama birden çok binary crate olabilir. src/main.rs ve src/lib.rs aynı pakette birlikte yaşayabilir; bu, binary'nin kütüphaneyi tüketmesi için yaygın bir desendir (s07'de göreceğiz). Crate'in adı varsayılan olarak Cargo.toml içindeki paket adından gelir.

Cargo.toml
[package]
name = "mylib"      # crate kökündeki kanonik ad: crate::
version = "0.1.0"
edition = "2021"
src/lib.rs
// Bu dosya crate kökü = kök modülün gövdesi.
// Buraya yazılan her şey crate:: altında yer alır.

pub fn surum() -> &'static str {
    "0.1.0"
}

// Alt modüller burada bildirilir (s02-s03).
pub mod ag;
NOT

C alışkanlığı: "her .c ayrı çeviri birimi". Rust'ta bunu unutun — bir crate içindeki tüm .rs dosyaları, kökten dallanan tek bir modül ağacının parçalarıdır. Yeni bir dosya eklemek otomatik olarak derlemeye girmez; o dosyanın bir mod bildirimiyle ağaca bağlanması gerekir (s03).

Crate türünü çıktı açısından da düşünün: binary crate bir yürütülebilir üretir ve fn main() içermek zorundadır; library crate main içermez, başka crate'lerin use ile tükettiği bir API yüzeyi sağlar. Embedded tarafında --lib + no_std tipik bir başlangıçtır; donanım üzerinde koşan firmware genelde ayrı bir binary crate olur ve bu kütüphaneyi path bağımlılık olarak çeker (s08).

Bu bölümde

  • Crate'in Rust'ın derleme/dağıtım birimi olduğunu — C'deki yürütülebilir veya kütüphane artefaktının karşılığı — öğrendik
  • Binary crate kökü main.rs, library crate kökü lib.rs ayrımını netleştirdik
  • Kök modülün crate anahtar sözcüğüyle adreslendiğini gördük
  • Bir pakette tek lib + çok bin crate olabileceğini ve crate adının Cargo.toml'dan geldiğini öğrendik

02 mod ile modül tanımı

mod bir isim alanı (namespace) açar. C++'taki namespace bloğuna en yakın yapıdır, ama burada fn/struct/enum gibi öğeler modüle ait olur ve görünürlük varsayılan olarak modüle kapalıdır.

En basit haliyle modülü, içeriğini süslü parantez içine alarak satır içinde (inline) tanımlarsınız. Bu, C++'ta namespace ag { ... } yazmaya benzer; ancak burada her item modülün üyesi olur ve dışarıdan erişim için yolunu (path) bilmeniz gerekir.

src/lib.rs
mod ag {
    // Modüle ait bir struct (varsayılan: private).
    pub struct Soket {
        pub port: u16,
    }

    pub fn baglan(adres: &str) -> Soket {
        // ... gerçek bağlanma mantığı ...
        Soket { port: 8080 }
    }

    // Modül içinde başka modül de açılabilir (iç içe ağaç).
    pub mod protokol {
        pub enum Tip { Tcp, Udp }
    }
}

fn main_benzeri() {
    // Dış modülden çağrı: tam yol ile.
    let s = ag::baglan("10.0.0.1:8080");
    let _ = s.port;
    let _t = ag::protokol::Tip::Tcp;
}

Bu örnekte oluşan modül ağacına dikkat edin. Kök modülün altında ag, onun altında protokol vardır. Her öğenin kanonik bir yolu bulunur: crate::ag::Soket, crate::ag::baglan, crate::ag::protokol::Tip.

crate → ag → { Soket, baglan, protokol → { Tip } }
mod ag { ... }Satır içi modül; gövdesi süslü parantez içinde, aynı dosyada.
mod ag;Modülün gövdesini ayrı bir dosyadan oku (s03). Noktalı virgül, parantez değil.
pub modModülün kendisini dışarıya görünür kıl; içindeki öğeler ayrıca pub olmalı.
DİKKAT

Modülü pub mod ag yapmak, içindekileri otomatik açmaz. ag görünür olsa bile ag::baglan yalnızca baglan da pub ise dışarıdan erişilebilir. Görünürlük her öğe için ayrı ayrı belirlenir (s05). C'deki "header'a koyarsam dışarı açılır" sezgisi burada geçerli değildir.

Modül yalnızca bir isim kabı değil; aynı zamanda gizlilik sınırıdır. Modül içinde tanımlı private bir yardımcı fonksiyon, aynı modülün (ve alt modüllerinin) her yerinden çağrılabilir ama dışarıya sızmaz. Bu, C'deki dosya kapsamlı static fonksiyona benzer; fark, sınırın dosya değil modül olmasıdır — birden çok dosyaya yayılmış olsa bile.

Bu bölümde

  • mod ag { ... } ile satır içi modül tanımını ve C++ namespace ile benzerliğini gördük
  • fn/struct/enum gibi öğelerin modüle ait olduğunu ve kanonik yola (crate::ag::Soket) sahip olduğunu öğrendik
  • Modüllerin iç içe geçerek bir ağaç oluşturduğunu kavradık
  • Modülün hem isim alanı hem de gizlilik sınırı olduğunu netleştirdik

03 Modülleri dosyalara bölmek

mod foo; (gövdesiz, noktalı virgülle), derleyiciye modül içeriğini başka bir dosyadan yüklemesini söyler. Bu, C'deki #include "foo.h"'in kavramsal karşılığıdır — ama metin kopyalamaz, modül ağacına bir düğüm bağlar.

Tek bir lib.rs büyüdükçe öğeleri dosyalara taşırsınız. Anahtar kural: dosya, modülü içermez — dosya modülün gövdesidir. foo.rs dosyasının içinde tekrar mod foo { ... } yazmazsınız; kök dosyada mod foo; dediğinizde foo.rs'nin tüm içeriği foo modülünün gövdesi olur.

src/lib.rs
// "foo modülünü, dosyasından yükle" — #include değil, bağlama.
pub mod ag;        // → src/ag.rs   veya   src/ag/mod.rs
mod dahili;        // → src/dahili.rs (private modül)

2018 edition'dan beri tercih edilen yerleşim, modül için foo.rs dosyası ve onun alt modülleri için foo/ dizinidir. Eski foo/mod.rs deseni hâlâ geçerlidir ama yeni kodda foo.rs + foo/ önerilir (çok sayıda mod.rs dosyasını editörde ayırt etmek zordur).

mod bildirimi (üst dosya)Aranan dosya yolu (2018+ tercih)Eski biçim
mod ag; (lib.rs içinde)src/ag.rssrc/ag/mod.rs
mod protokol; (ag.rs içinde)src/ag/protokol.rssrc/ag/protokol/mod.rs
mod tcp; (protokol.rs içinde)src/ag/protokol/tcp.rssrc/ag/protokol/tcp/mod.rs

Alt modül bildirimi nereye yazılır? Bir modülün alt modülleri, o modülün dosyasında bildirilir. Yani ag modülünün protokol alt modülü lib.rs'te değil, ag.rs'te (veya ag/mod.rs'te) mod protokol; ile bildirilir. Tipik bir ağaç:

bash
# Dizin ağacı — 2018+ tercih edilen yerleşim
src/
├── lib.rs              # crate kökü: mod ag;  mod dahili;
├── dahili.rs           # crate::dahili
├── ag.rs               # crate::ag — içinde: mod protokol;
└── ag/
    ├── protokol.rs     # crate::ag::protokol — içinde: mod tcp; mod udp;
    └── protokol/
        ├── tcp.rs      # crate::ag::protokol::tcp
        └── udp.rs      # crate::ag::protokol::udp
src/ag.rs
// Bu dosyanın tamamı = crate::ag modülünün gövdesi.
// Alt modülü burada bildiriyoruz (lib.rs'te değil).
pub mod protokol;

pub struct Soket { pub port: u16 }

pub fn baglan(_a: &str) -> Soket {
    Soket { port: 8080 }
}
DİKKAT

Yeni bir .rs dosyası oluşturmak yetmez — bir mod bildirimiyle ağaca bağlanmadıkça derleyici onu tamamen yok sayar (derlenmez, hata bile vermez). C'de Makefile'a dosya eklemeyi unutmaya benzer, ama burada hata sessizdir: "yazdığım kod neden çalışmıyor?" sorusunun bir numaralı cevabı, eksik mod bildirimidir.

Bu bölümde

  • mod foo;'nin modül gövdesini ayrı dosyadan yüklediğini, #include'un kavramsal ama metinsel olmayan karşılığı olduğunu öğrendik
  • Dosyanın modülü içermediğini — dosyanın modülün gövdesi olduğunu kavradık
  • 2018+ tercih edilen foo.rs + foo/ yerleşimini ve eski foo/mod.rs biçimini karşılaştırdık
  • Alt modülün üst modülün dosyasında bildirildiğini ve bağlanmamış dosyanın sessizce yok sayıldığını gördük

04 use ve yollar (path)

Bir öğeye iki şekilde ulaşırsınız: mutlak yol kökten (crate::ag::baglan) ya da göreli yol bulunduğunuz modülden (self::, super::). use ise uzun bir yolu kapsama getirip kısaltmaktır — C++'taki using bildirimine benzer.

Yol, modül ağacında bir öğeye giden adres çubuğudur. Mutlak yol crate:: ile başlar (crate kökü). Göreli yol mevcut modülden hareket eder: self:: bu modül, super:: bir üst modül (dosya sisteminde .. gibi). Dış bir crate'e atıf, o crate'in adıyla başlar: std::collections::HashMap.

src/ag/protokol.rs
// Konum: crate::ag::protokol

pub fn el_sikis() {
    // Mutlak yol: kökten itibaren.
    let _s = crate::ag::baglan("127.0.0.1:80");

    // Göreli yol: bir üst modüldeki (crate::ag) öğeye.
    let _s2 = super::baglan("127.0.0.1:80");

    // self:: = bu modül; aynı modüldeki yardımcıya.
    self::dogrula();
}

fn dogrula() { /* ... */ }

use bir yolu kapsama getirir; sonrasında kısa adıyla anabilirsiniz. Bu, sembolü kopyalamaz — yalnızca yerel bir takma ad oluşturur.

src/main.rs
// Tek öğe getir.
use std::collections::HashMap;

// Aynı modülden birden çok öğe — gruplu (nested) import.
use std::io::{Read, Write, BufReader};

// İç crate yolunu kısalt.
use crate::ag::protokol::Tip;

// İsim çakışması: as ile yeniden adlandır.
use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;

// Glob: modüldeki tüm public öğeleri getir (dikkatli kullan).
use crate::ag::protokol::*;

fn main() {
    let _m: HashMap<String, u32> = HashMap::new();
    let _t = Tip::Tcp;
}
crate::Mutlak yol kökü; mevcut crate'in kök modülü.
self::Göreli; içinde bulunulan modül.
super::Göreli; bir üst modül (dosya sisteminde .. gibi).
use a::b::{X, Y}Gruplu import; aynı önekten birden çok öğe.
use a::b::* Glob import; tüm public öğeler. İsim kirliliği riski.
use a::b as cYeniden adlandırma; çakışmaları çözer.
DİKKAT

Glob import (use foo::*) C'deki using namespace std; alışkanlığını çağrıştırır ve aynı tehlikeyi taşır: hangi ismin nereden geldiği belirsizleşir ve yeni bir öğe eklenmesi sessizce çakışma yaratabilir. İstisna, kasıtlı bir prelude modülüdür (s06). Test modüllerinde use super::*; kabul görür; üretim kodunda açık import tercih edin.

C/C++'taki using ile fark: Rust'ta use de bir öğedir ve görünürlüğü vardır. pub use yazarsanız getirdiğiniz ismi dışarıya da yeniden yayımlarsınız — bu, temiz API kurmanın anahtarıdır (s06).

Bu bölümde

  • Mutlak (crate::) ve göreli (self::, super::) yol ayrımını öğrendik
  • use'un yolu kısalttığını, sembolü kopyalamadığını; gruplu import ve as ile çakışma çözmeyi gördük
  • Glob import'un riskini ve C'deki using namespace ile paralelliğini kavradık
  • use'un kendisinin bir öğe olduğunu ve pub use ile yeniden yayımlanabildiğini fark ettik

05 Görünürlük

Rust'ta varsayılan görünürlük private'tir — bir öğe yalnızca tanımlandığı modülden (ve alt modüllerinden) erişilebilir. pub ile açarsınız; pub(crate), pub(super), pub(in yol) ile bu açılışı tam olarak ayarlarsınız.

C'deki kaba ikiliği hatırlayın: static (dosya kapsamı) ya da harici bağlantı (global). Rust çok daha incelikli bir gradyan sunar. Varsayılan static gibi düşünülebilir ama sınır dosya değil modüldür. pub ise "her yere açık" demek değildir — yalnızca öğeyi içeren modül erişilebilir olduğu sürece açıktır.

BildirimErişim sınırıC/C++ benzeri sezgi
(yok) varsayılanTanımlandığı modül + alt modüllerdosya kapsamlı static
pubModül görünür olduğu her yerharici bağlantı / public API
pub(crate)Yalnızca bu crate içikütüphane-içi "internal"
pub(super)Bir üst modül ve onun alt ağacı(C'de doğrudan yok)
pub(in crate::a)Belirtilen ata modül alt ağacı(C'de doğrudan yok)
src/ag.rs
pub mod protokol;

// Dış API: her yerden erişilebilir (crate görünür olduğunda).
pub fn baglan(_a: &str) -> Soket { Soket { port: 8080, anahtar: 0 } }

// Sadece bu crate içinde paylaşılan yardımcı.
pub(crate) fn dahili_log(_m: &str) { /* ... */ }

// Tamamen private — yalnızca ag ve alt modülleri.
fn ham_yaz(_b: &[u8]) { /* ... */ }

pub struct Soket {
    pub port: u16,        // public alan
    anahtar: u64,         // private alan: struct pub olsa da gizli
}
NOT

Struct'ın pub olması alanlarını açmaz. Yukarıda Soket dışarıdan kurulabilir değildir, çünkü anahtar alanı private'tir — dışarıdaki kod tüm alanları belirten bir Soket { .. } ifadesi yazamaz. Bu, C++'ta tüm üyeleri private yapıp yalnızca bir factory fonksiyonu (baglan) sunmaya denktir; değişmezleri (invariant) korumanın temel yoludur. Enum'larda ise tersi geçerli: pub enum tüm varyantlarını otomatik public yapar.

pub(crate) pratikte en çok kullanılan ara seviyedir: bir öğeyi crate'in her yerinde paylaşmak ama dışarıya (kütüphaneyi tüketenlere) sızdırmamak istediğinizde. C'de bu, "header'a koymadan, birkaç .c arasında extern ile paylaşılan" sembollere benzer — ama Rust'ta niyet açık ve derleyici tarafından zorlanır.

Bu bölümde

  • Varsayılanın private olduğunu ve sınırın dosya değil modül olduğunu kavradık
  • pub, pub(crate), pub(super), pub(in yol) gradyanını ve C static/extern sezgileriyle eşleştirdik
  • Struct alan görünürlüğünün öğe görünürlüğünden ayrı olduğunu; private alanın dışarıdan kurmayı engellediğini gördük
  • pub enum'un tüm varyantları açtığını ve pub(crate)'in en yaygın ara seviye olduğunu öğrendik

06 Re-export (pub use)

pub use bir öğeyi başka bir konumdan yeniden yayımlar. İç modül yapısını derinlerde tutup, kullanıcıya yassı (flat) ve temiz bir public API yüzeyi sunmanın yoludur — kütüphanenin "façade"ı.

İyi düzenlenmiş bir kütüphanenin iç modül ağacı derin olabilir: crate::ag::protokol::tcp::baglanti::Baglanti. Kullanıcıya bu derinliği dayatmak istemezsiniz. pub use ile derindeki öğeyi crate kökünden tek isimle açabilirsiniz. C dünyasında karşılığı, birçok iç .h'yi tek bir "umbrella header"da #include edip kullanıcıya yalnızca onu sunmaktır — ama burada isim alanı korunur, metin kopyalanmaz.

src/lib.rs
// İç ağaç private kalsın; ayrıntı dışarı sızmasın.
mod ag;
mod kodlama;

// Public API yüzeyi: derindeki öğeleri kökten yeniden yayımla.
pub use ag::Soket;
pub use ag::baglan;
pub use ag::protokol::Tip;
pub use kodlama::{encode, decode};

Artık kullanıcı, iç yerleşimden habersiz, her şeyi crate kökünden çeker:

kullanıcı tarafı
// Derin yol yerine yassı, kararlı API:
use mylib::{Soket, baglan, Tip, encode};

// mylib::ag::protokol::Tip değil — sadece mylib::Tip.
NOT

Re-export, API kararlılığı (stability) için kritiktir. İç modülleri yeniden düzenleseniz bile (ag'yi bölmek, taşımak), pub use satırlarını güncelleyerek kullanıcıya görünen yüzeyi aynı tutarsınız. Kullanıcının use mylib::Soket; kodu kırılmaz. Bu, C'de "implementasyonu değiştir ama public header'ı koru" disiplininin dil tarafından desteklenen halidir.

Prelude deseni: std ve birçok crate, en sık kullanılan öğeleri bir prelude modülünde toplar; kullanıcı tek bir glob ile hepsini getirir. Bu, glob import'un meşru kullanımıdır çünkü prelude'un içeriği kasıtlı ve dikkatle seçilmiştir.

src/lib.rs
pub mod prelude {
    // En sık kullanılanları tek noktada topla.
    pub use crate::Soket;
    pub use crate::baglan;
    pub use crate::Tip;
}

// Kullanıcı: use mylib::prelude::*;  → kasıtlı, kontrollü glob.

Bu bölümde

  • pub use'un derin iç öğeleri kökten yeniden yayımlayarak yassı public API kurduğunu öğrendik
  • İç modülleri private tutup yalnızca façade'ı açmanın API kararlılığı sağladığını gördük
  • İç yeniden düzenlemenin kullanıcı kodunu kırmadan yapılabildiğini kavradık
  • Prelude desenini ve glob import'un bu kasıtlı kullanımının neden meşru olduğunu öğrendik

07 Birden çok hedef

Tek bir paket birden çok yürütülebilir, örnekler, testler ve benchmark'lar barındırabilir. Cargo bunları konvansiyonla keşfeder — ayrı build hedefi tanımlamaya gerek yoktur, doğru dizine doğru dosyayı koyarsınız.

C/C++'ta her yürütülebilir için Makefile/CMake hedefini elle tanımlarsınız. Cargo'da yerleşim kuralı bunu otomatikleştirir. Bir kütüphane (src/lib.rs) ve onu tüketen birden çok binary aynı pakette yaşar.

YerleşimHedef türüÇalıştırma / amaç
src/main.rsBirincil binarycargo run
src/bin/*.rsEk binary (her dosya bir tane)cargo run --bin ad
src/lib.rsLibrary cratebinary'ler ve dış crate'ler tüketir
examples/*.rsÖrnek programlarcargo run --example ad
tests/*.rsEntegrasyon testlericargo test
benches/*.rsBenchmark'larcargo bench
bash
# lib + çoklu bin + örnek + entegrasyon testi aynı pakette
src/
├── lib.rs           # crate::  (kütüphane API'si)
├── main.rs          # varsayılan binary: cargo run
└── bin/
    ├── sunucu.rs    # cargo run --bin sunucu
    └── istemci.rs   # cargo run --bin istemci
examples/
└── demo.rs          # cargo run --example demo
tests/
└── entegrasyon.rs   # cargo test  (dışarıdan, public API üzerinden)
benches/
└── throughput.rs    # cargo bench

Kritik nokta: src/bin/, examples/ ve tests/ içindeki her dosya ayrı bir crate'tir ve kütüphaneyi dışarıdan, paket adıyla tüketir — kendi src/main.rs'iniz dahil. Yani entegrasyon testi tıpkı bir kullanıcı gibi yalnızca public API'yi görür; bu, API'nizin gerçekten kullanılabilir olduğunu doğrulamanın iyi bir yoludur.

src/bin/sunucu.rs
// Ayrı bir crate; kütüphaneyi paket adıyla tüketir.
use mylib::{baglan, Soket};

fn main() {
    let s: Soket = baglan("0.0.0.0:8080");
    println!("port: {}", s.port);
}
DİKKAT

Birim testleri (unit test) bu kuralın istisnasıdır: #[cfg(test)] mod tests { ... } bloğu kaynak dosyanın içinde yaşar ve private öğelere erişebilir. tests/ dizinindeki entegrasyon testleri ise dış crate olduğundan yalnızca pub öğeleri görür. İkisini karıştırmayın: iç mantığı birim testiyle, API sözleşmesini entegrasyon testiyle doğrulayın.

Bu bölümde

  • Tek pakette lib + src/bin/* çoklu binary + examples/, tests/, benches/ yerleşimini öğrendik
  • Cargo'nun hedefleri konvansiyonla keşfettiğini, ayrı hedef tanımlamaya gerek olmadığını gördük
  • bin/example/integration-test dosyalarının ayrı crate olup kütüphaneyi public API üzerinden tükettiğini kavradık
  • Birim testinin (kaynak içi, private erişim) ile entegrasyon testinin (dış crate, yalnızca pub) farkını netleştirdik

08 Workspace

Workspace, birden çok crate'i tek bir Cargo.lock ve ortak target/ altında toplayan üst yapıdır. Büyük bir projeyi mantıksal kütüphanelere bölmenin ve aralarında path bağımlılığı kurmanın yolu.

Proje büyüdükçe tek bir crate hantallaşır. Çözüm: alanları (domain) ayrı crate'lere bölmek ve hepsini bir workspace altında toplamak. Bu, C dünyasında bir mono-repo içinde birden çok kütüphane + uygulama hedefini tek bir üst CMake projesiyle yönetmeye benzer — ama bağımlılık çözümü, sürüm kilitleme ve paylaşımlı derleme önbelleği yerleşiktir.

Cargo.toml
# Kök Cargo.toml — workspace tanımı (kendi [package]'ı yok)
[workspace]
resolver = "2"
members = [
    "crates/cekirdek",
    "crates/protokol",
    "apps/sunucu",
]

# Tüm üyelerin paylaştığı bağımlılık sürümleri (opsiyonel).
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
bash
# Workspace dizin yapısı
.
├── Cargo.toml          # [workspace] kökü
├── Cargo.lock          # TÜM crate'ler için TEK kilit dosyası
├── target/             # ortak derleme çıktısı (paylaşımlı)
├── crates/
│   ├── cekirdek/       # lib crate
│   │   ├── Cargo.toml
│   │   └── src/lib.rs
│   └── protokol/       # cekirdek'e bağımlı lib crate
│       ├── Cargo.toml
│       └── src/lib.rs
└── apps/
    └── sunucu/         # bin crate; iki kütüphaneyi de çeker
        ├── Cargo.toml
        └── src/main.rs

Workspace içindeki crate'ler birbirini path bağımlılığı ile çeker. Bu, crates.io'ya yayımlamaya gerek kalmadan iç kütüphaneleri kullanmanın yoludur:

apps/sunucu/Cargo.toml
[package]
name = "sunucu"
version = "0.1.0"
edition = "2021"

[dependencies]
# İç crate'leri yerel yol ile bağla.
cekirdek = { path = "../../crates/cekirdek" }
protokol = { path = "../../crates/protokol" }
# Ortak workspace bağımlılığını miras al.
serde = { workspace = true }
membersWorkspace'e dahil crate yolları. Glob (crates/*) da desteklenir.
Cargo.lockTüm üyeler için tek; sürümler workspace genelinde tutarlı kalır.
target/Paylaşımlı çıktı dizini; bir kez derlenen bağımlılık tüm crate'lerce kullanılır.
path = "..."Yerel crate'e bağımlılık; crates.io'ya gerek yok.
NOT

Tek Cargo.lock ve paylaşımlı target/ iki büyük kazanç sağlar: (1) tüm crate'ler aynı bağımlılık sürümlerinde kilitlenir, sürüm sapması (diamond dependency) sorunları azalır; (2) ortak bir bağımlılık (örn. serde) yalnızca bir kez derlenir ve tüm üyelerce paylaşılır — derleme süresinden kazanırsınız. cargo build kökten çalıştırıldığında tüm workspace'i, cargo build -p protokol ise yalnızca o crate'i derler.

Bu bölümde

  • Workspace'in çok-crate projeyi tek Cargo.lock + ortak target/ altında topladığını öğrendik
  • [workspace] members ile crate listelemeyi ve dizin yapısını gördük
  • path = "..." path bağımlılığı ile iç kütüphaneleri yayımlamadan kullanmayı kavradık
  • Tek kilit + paylaşımlı derleme önbelleğinin sürüm tutarlılığı ve derleme hızı kazancını öğrendik

09 Gerçek örnek

Şimdi öğrendiklerimizi tek bir küçük workspace'te birleştirelim: bir sensor library crate ve onu tüketen bir okuyucu binary crate. Modül ağacı, pub use ile temiz API ve bilinçli görünürlük seçimleriyle.

Senaryo: bir sıcaklık sensöründen veri okuyan, ham byte'ları çözümleyip ölçüm üreten küçük bir kütüphane. İç çözümleme mantığı gizli kalmalı; kullanıcı yalnızca Sensor, Olcum ve oku'yu görmeli. Dizin ağacı:

bash
.
├── Cargo.toml              # [workspace]
├── crates/
│   └── sensor/
│       ├── Cargo.toml
│       └── src/
│           ├── lib.rs      # public façade (pub use)
│           ├── cihaz.rs    # Sensor, Olcum
│           └── cihaz/
│               └── cozumle.rs   # private çözümleme
└── apps/
    └── okuyucu/
        ├── Cargo.toml
        └── src/main.rs     # bin crate
Cargo.toml
[workspace]
resolver = "2"
members = ["crates/sensor", "apps/okuyucu"]
crates/sensor/src/lib.rs
// crate kökü = public façade.
// İç modülü private tut; sadece seçili öğeleri yeniden yayımla.
mod cihaz;

pub use cihaz::{Sensor, Olcum, oku};

// Kullanıcı yalnızca bunları görür:
//   sensor::Sensor, sensor::Olcum, sensor::oku
crates/sensor/src/cihaz.rs
// crate::cihaz — alt modülü burada bildir, ama private bırak.
mod cozumle;

use self::cozumle::ham_to_santigrat;   // göreli yol, iç kullanım

pub struct Olcum {
    pub santigrat: f32,   // okunur veri: public alan
}

pub struct Sensor {
    adres: u8,             // private: değişmez korunur, dışarıdan kurulamaz
}

impl Sensor {
    // Tek giriş kapısı: factory (private alan yüzünden zorunlu).
    pub fn yeni(adres: u8) -> Sensor {
        Sensor { adres }
    }
}

// Serbest fonksiyon: bir Sensor'dan ölçüm üret.
pub fn oku(s: &Sensor) -> Olcum {
    let ham = donanim_oku(s.adres);          // crate-içi yardımcı
    Olcum { santigrat: ham_to_santigrat(ham) }
}

// Yalnızca bu crate içi paylaşılan yardımcı; API'ye sızmaz.
pub(crate) fn donanim_oku(_adres: u8) -> u16 {
    // ... I2C/SPI okuması ... (örnek: sabit)
    0x0190
}
crates/sensor/src/cihaz/cozumle.rs
// crate::cihaz::cozumle — tamamen private çözümleme mantığı.
// pub değil → kullanıcıya hiç görünmez; serbestçe değiştirilebilir.

pub(super) fn ham_to_santigrat(ham: u16) -> f32 {
    // 12-bit ADC, 0.0625 °C/LSB varsayımı.
    (ham as f32) * 0.0625
}
NOT

Görünürlük seçimlerine dikkat: ham_to_santigrat pub(super) — yalnızca üst modül (cihaz) onu çağırabilir, crate'in kalanına bile açık değildir. donanim_oku ise pub(crate) — crate içi paylaşılır ama dış API'ye sızmaz. Sensor public ama adres alanı private olduğundan yalnızca Sensor::yeni ile kurulur. Bu üç seviye, "ne kadar gerekiyorsa o kadar aç" prensibinin somut uygulamasıdır.

apps/okuyucu/src/main.rs
// Kullanıcı yalnızca temiz façade'ı görür — iç ağaçtan habersiz.
use sensor::{Sensor, oku};

fn main() {
    let s = Sensor::yeni(0x48);
    let m = oku(&s);
    println!("sıcaklık: {:.2} °C", m.santigrat);

    // Aşağıdakiler DERLENMEZ — kasıtlı olarak gizli:
    //   sensor::donanim_oku(0x48);     // pub(crate): dışarı kapalı
    //   sensor::cihaz::cozumle::...;   // mod cihaz private
    //   Sensor { adres: 0x48 };        // alan private
}

Tüm parçalar bir arada: workspace iki crate'i bağlar; lib crate iç modül ağacını (cihazcozumle) gizler ve pub use ile üç isimlik yassı bir yüzey sunar; pub(crate)/pub(super) ile her yardımcı tam gerektiği kadar açıktır; private alanlar değişmezleri korur. cargo run -p okuyucu çalışır, kullanıcı yalnızca sözleşmeyi görür. İç ağacı dilediğiniz gibi yeniden düzenleyebilirsiniz — lib.rs'teki pub use satırları aynı kaldıkça kullanıcı kodu kırılmaz.

Bu bölümde

  • Bir lib + bir bin crate'lik küçük workspace'i baştan sona kurduk
  • İç modül ağacını (cihaz/cozumle) gizleyip pub use ile üç isimlik temiz API sunduk
  • pub(super), pub(crate), private alan ve public alan seçimlerini gerekçeleriyle uyguladık
  • İç yeniden düzenlemenin façade sabit kaldıkça kullanıcı kodunu kırmadığını somut kodla doğruladık