Cargo ile Rust Proje ve Paket Yönetimi

Rust ile ciddi bir proje geliştirmeye başladığınızda, ilk fark ettiğiniz şey Cargo’nun ne kadar iyi düşünülmüş bir araç olduğudur. Paket yönetimi, derleme, test, dokümantasyon üretimi… Hepsi tek bir araçta toplanmış. Node.js’teki npm’e veya Python’daki pip’e alışkın olanlar için Cargo biraz daha kapsamlı geliyor, ancak bu kapsamlılık bir süre sonra gerçek bir özgürlüğe dönüşüyor. Bu yazıda Cargo’yu sıfırdan ele alacak, gerçek dünya senaryolarıyla proje ve paket yönetimini öğreneceğiz.

Cargo Nedir ve Neden Önemlidir

Cargo, Rust’ın resmi paket yöneticisi ve derleme sistemidir. Rust ekosisteminde “crate” olarak adlandırılan paketler, [crates.io](https://crates.io) üzerinde yayınlanır ve Cargo aracılığıyla projelere dahil edilir. Go’nun go mod sistemine benzer bir yapısı var, ancak çok daha olgun ve özellik açısından zengin.

Cargo şu işleri yapar:

  • Projeyi oluşturur ve yönetir
  • Bağımlılıkları indirir, derler ve yönetir
  • Kodunuzu derler (hem debug hem release modda)
  • Testleri çalıştırır
  • Dokümantasyon üretir
  • Paketi crates.io’ya yayınlar
  • Workspace (çok projeli) yapıları yönetir

Rust’ı sisteminize rustup ile kurduğunuzda Cargo otomatik olarak gelir. Kurulum yapmadıysanız şu komutla başlayabilirsiniz:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
cargo --version

İlk Projeyi Oluşturmak

Cargo ile yeni bir proje oluşturmak iki farklı şekilde yapılır. Binary (çalıştırılabilir) projeler için --bin, kütüphane projeleri için --lib bayrağını kullanırsınız.

# Çalıştırılabilir proje oluştur
cargo new hello_sysadmin --bin

# Kütüphane projesi oluştur
cargo new mylib --lib

# Mevcut dizini Cargo projesine dönüştür
cargo init

Oluşturulan proje yapısına bakalım:

cd hello_sysadmin
tree .

Çıktı şöyle görünür:

hello_sysadmin/
├── Cargo.toml
└── src/
    └── main.rs

Cargo.toml dosyası projenin kalbidir. İlk halinde şöyle görünür:

[package]
name = "hello_sysadmin"
version = "0.1.0"
edition = "2021"

[dependencies]

Projeyi derleyip çalıştırmak için:

cargo run

Bu komut projeyi derler ve hemen çalıştırır. İlk çalıştırmada biraz bekleyebilirsiniz çünkü Cargo bağımlılıkları da derler.

Cargo.toml Dosyasını Anlamak

Gerçek bir projede Cargo.toml çok daha karmaşık bir hal alır. Bir sistem izleme aracı yazdığınızı düşünelim. Bağımlılıklarla dolu bir Cargo.toml şöyle olabilir:

[package]
name = "sysmon"
version = "0.2.1"
edition = "2021"
authors = ["Ahmet Yilmaz <[email protected]>"]
description = "Sistem izleme aracı"
license = "MIT"
repository = "https://github.com/ahmet/sysmon"
readme = "README.md"
keywords = ["sysadmin", "monitoring", "linux"]
categories = ["command-line-utilities"]

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
clap = { version = "4.0", features = ["derive"] }
anyhow = "1.0"
reqwest = { version = "0.11", features = ["json"] }

[dev-dependencies]
tokio-test = "0.4"
assert_cmd = "2.0"
tempfile = "3.0"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true

[profile.dev]
opt-level = 0
debug = true

Burada dikkat edilmesi gereken önemli noktalar var:

  • [dependencies]: Üretim ortamında kullanılacak bağımlılıklar
  • [dev-dependencies]: Sadece test ve geliştirme sırasında kullanılacak bağımlılıklar
  • [profile.release]: Release derlemesi için optimizasyon ayarları
  • features: Bazı crate’ler isteğe bağlı özellikler sunar, bunları burada aktive edersiniz

serde = { version = "1.0", features = ["derive"] } ifadesi, serde crate’inin 1.0 ve üzeri sürümünü, “derive” özelliğiyle birlikte kullan demektir.

Bağımlılık Yönetimi

Versiyon Belirtme Kuralları

Cargo, semantik versiyonlama (semver) kullanır. Versiyon belirtirken birkaç farklı syntax kullanabilirsiniz:

  • “1.0”: 1.0.0’dan 2.0.0’a kadar (2.0.0 hariç) her sürümü kabul et
  • “=1.2.3”: Tam olarak bu sürümü kullan
  • “>=1.0, <2.0": Bu aralıktaki sürümleri kabul et
  • “~1.2”: 1.2.x sürümlerini kabul et
  • “*”: Herhangi bir sürümü kabul et (önerilmez)

Bağımlılık Ekleme ve Güncelleme

# Bağımlılık ekle (cargo-edit kuruluysa)
cargo add serde --features derive
cargo add tokio --features full
cargo add --dev mockall

# Bağımlılıkları güncelle
cargo update

# Belirli bir crate'i güncelle
cargo update -p serde

# Bağımlılık ağacını görüntüle
cargo tree

# Belirli bir paketin neden dependency'de olduğunu göster
cargo tree -i tokio

cargo-edit aracını kurmak için:

cargo install cargo-edit

Cargo.lock Dosyası

Cargo.lock dosyası, tam olarak hangi sürümlerin kullanıldığını kaydeder. Bu dosya hakkında şu kuralı aklınızda tutun:

Binary projelerinde Cargo.lock dosyasını Git’e ekleyin. Böylece tüm ekip üyeleri ve CI/CD sistemleri aynı sürümleri kullanır. Kütüphane projelerinde ise bu dosyayı .gitignore‘a ekleyin.

Derleme Seçenekleri

# Debug derleme (varsayılan, hızlı derleme, yavaş çalışma)
cargo build

# Release derleme (yavaş derleme, hızlı çalışma)
cargo build --release

# Sadece hataları kontrol et, binary üretme (çok hızlı)
cargo check

# Belirli bir hedef için derle (cross-compilation)
cargo build --target x86_64-unknown-linux-musl

# Tüm özellikleri aktive ederek derle
cargo build --all-features

# Belirli özellikleri aktive et
cargo build --features "feature1,feature2"

Cross-compilation özellikle container veya embedded sistemler için kritik önem taşır. Örneğin Alpine Linux için statik binary üretmek isterseniz:

# Hedef toolchain ekle
rustup target add x86_64-unknown-linux-musl

# Statik binary derle
cargo build --release --target x86_64-unknown-linux-musl

# Binary boyutunu kontrol et
ls -lh target/x86_64-unknown-linux-musl/release/sysmon

Test Altyapısı

Cargo’nun yerleşik test sistemi oldukça güçlüdür. Unit testler doğrudan kaynak dosyaların içine yazılır:

// src/parser.rs içinde
pub fn parse_memory_info(line: &str) -> Option<u64> {
    let parts: Vec<&str> = line.split_whitespace().collect();
    if parts.len() >= 2 {
        parts[1].parse().ok()
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_valid_memory_line() {
        let line = "MemTotal: 16384 kB";
        assert_eq!(parse_memory_info(line), Some(16384));
    }

    #[test]
    fn test_parse_invalid_line() {
        let line = "invalid_line";
        assert_eq!(parse_memory_info(line), None);
    }
}

Testleri çalıştırmak için:

# Tüm testleri çalıştır
cargo test

# Belirli bir testi çalıştır
cargo test test_parse_valid_memory_line

# Test çıktısını göster (normalde başarılı testlerin çıktısı gizlenir)
cargo test -- --nocapture

# Paralel test sayısını sınırla
cargo test -- --test-threads=1

# Integration testleri çalıştır
cargo test --test integration_tests

# Doc testlerini de çalıştır
cargo test --doc

Integration testleri tests/ dizinine yerleştirilir:

mkdir tests
cat > tests/integration_tests.rs << 'EOF'
use assert_cmd::Command;

#[test]
fn test_sysmon_runs_successfully() {
    let mut cmd = Command::cargo_bin("sysmon").unwrap();
    cmd.arg("--version").assert().success();
}

#[test]
fn test_sysmon_cpu_output() {
    let mut cmd = Command::cargo_bin("sysmon").unwrap();
    cmd.arg("cpu")
       .assert()
       .success()
       .stdout(predicates::str::contains("CPU"));
}
EOF

Dokümantasyon Üretimi

Cargo, kodunuzdaki /// yorumlarından otomatik HTML dokümantasyon üretir:

# Dokümantasyon üret
cargo doc

# Üret ve tarayıcıda aç
cargo doc --open

# Bağımlılıkların dokümantasyonunu da dahil et
cargo doc --no-deps

# Private item'ları da dokümante et
cargo doc --document-private-items

Rust’ta dokümantasyon yorumları şöyle yazılır:

/// Sistem bellek bilgilerini okur ve MB cinsinden döner.
///
/// # Örnekler
///
/// ```
/// let mem = get_total_memory().unwrap();
/// assert!(mem > 0);
/// ```
///
/// # Hatalar
///
/// `/proc/meminfo` okunamazsa `anyhow::Error` döner.
pub fn get_total_memory() -> anyhow::Result<u64> {
    // implementasyon
}

Bu syntax’la yazılan örnekler aynı zamanda test olarak da çalıştırılabilir. Yani dokümantasyonunuz her zaman güncel kalır.

Workspace Yapısı

Büyük projelerde birden fazla crate’i tek bir workspace altında yönetmek istersiniz. Örneğin bir DevOps araçları paketi geliştirdiğinizi düşünelim:

mkdir devops-toolkit
cd devops-toolkit

# Root Cargo.toml oluştur
cat > Cargo.toml << 'EOF'
[workspace]
members = [
    "sysmon",
    "logparser",
    "alerter",
    "shared-types",
]
resolver = "2"
EOF

# Alt projeleri oluştur
cargo new sysmon --bin
cargo new logparser --bin
cargo new alerter --bin
cargo new shared-types --lib

Workspace’teki projelerde ortak bağımlılıkları merkezi olarak yönetmek için workspace.dependencies kullanılır:

[workspace]
members = ["sysmon", "logparser", "alerter", "shared-types"]
resolver = "2"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
tracing = "0.1"

Alt projelerin Cargo.toml dosyalarında bu bağımlılıkları şöyle kullanabilirsiniz:

[dependencies]
serde = { workspace = true }
tokio = { workspace = true }
shared-types = { path = "../shared-types" }

Workspace’te derleme ve test:

# Tüm workspace'i derle
cargo build --workspace

# Belirli bir paketi derle
cargo build -p sysmon

# Tüm testleri çalıştır
cargo test --workspace

# Tüm paketleri kontrol et
cargo check --workspace

Faydalı Cargo Eklentileri

Cargo’nun yeteneklerini genişleten bir ekosistem var. Sysadminler için en kullanışlı olanlar:

# Güvenlik açıklarını tara
cargo install cargo-audit
cargo audit

# Bağımlılıkları güncelle (interaktif)
cargo install cargo-outdated
cargo outdated

# Kod kapsama raporu üret
cargo install cargo-tarpaulin
cargo tarpaulin --out Html

# Binary boyutunu analiz et
cargo install cargo-bloat
cargo bloat --release --crates

# Derleme süresini analiz et
cargo install cargo-timing
cargo build --timings

# Bağımlılıkları minimize et
cargo install cargo-machete
cargo machete

# Formatla
cargo fmt

# Lint kontrolü
cargo clippy -- -D warnings

cargo audit özellikle CI/CD pipeline’larında güvenlik kontrolü için kritik öneme sahiptir. Kullandığınız crate’lerde bilinen güvenlik açıkları varsa sizi uyarır.

Gerçek Dünya Senaryosu: CI/CD Pipeline

Bir Linux sunucusunda çalışan Rust projeniz için GitHub Actions veya GitLab CI konfigürasyonu hazırlayalım:

# .github/workflows/ci.yml
cat > .github/workflows/ci.yml << 'EOF'
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Rust toolchain kur
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy
      
      - name: Cache
        uses: Swatinem/rust-cache@v2
      
      - name: Format kontrol
        run: cargo fmt --check
      
      - name: Clippy
        run: cargo clippy --all-targets --all-features -- -D warnings
      
      - name: Testler
        run: cargo test --workspace
      
      - name: Güvenlik taraması
        run: |
          cargo install cargo-audit
          cargo audit

  build-release:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      
      - name: Rust toolchain kur
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: x86_64-unknown-linux-musl
      
      - name: Statik binary derle
        run: cargo build --release --target x86_64-unknown-linux-musl
      
      - name: Artifact yükle
        uses: actions/upload-artifact@v3
        with:
          name: sysmon-linux-amd64
          path: target/x86_64-unknown-linux-musl/release/sysmon
EOF

Cargo Environment Değişkenleri ve Konfigürasyon

Cargo’nun davranışını environment değişkenleriyle de kontrol edebilirsiniz:

  • CARGO_HOME: Cargo’nun paketleri sakladığı dizin (varsayılan: ~/.cargo)
  • RUSTUP_HOME: Rustup’ın toolchain’leri sakladığı dizin
  • CARGO_TARGET_DIR: Derleme çıktılarının konumu (CI’da paylaşılan cache için kullanışlı)
  • RUST_LOG: Log seviyesini belirler
  • RUSTFLAGS: Rust derleyicisine ek bayraklar geçer

Proje veya kullanıcı bazında Cargo konfigürasyonu .cargo/config.toml dosyasıyla yapılır:

# .cargo/config.toml
[build]
target = "x86_64-unknown-linux-musl"

[registries]
my-private-registry = { index = "https://registry.ornek.com/git" }

[net]
retry = 3

[alias]
b = "build"
t = "test"
r = "run"
rr = "run --release"

Bu alias’lar sayesinde cargo build yerine cargo b yazabilirsiniz.

Performans İpuçları

Özellikle büyük projelerde derleme sürelerini kısaltmak için:

# Incremental derlemeyi aktive et (zaten varsayılan olarak açık dev'de)
export CARGO_INCREMENTAL=1

# Paralel iş sayısını artır
export CARGO_BUILD_JOBS=8

# sccache ile derleme önbellekleme
cargo install sccache
export RUSTC_WRAPPER=sccache

# mold linker kullan (çok daha hızlı linkleme)
# Ubuntu'da: apt install mold
cat >> .cargo/config.toml << 'EOF'
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
EOF

sccache özellikle CI/CD ortamlarında derleme sürelerini dramatik biçimde kısaltır. Aynı kod birden fazla CI çalıştırmasında yeniden derlenmez.

Paket Yayımlama

Geliştirdiğiniz kütüphaneyi veya aracı crates.io’da yayımlamak istiyorsanız:

# Önce crates.io'da hesap açın ve API token alın
cargo login <API_TOKEN>

# Yayımlamadan önce kontrol et
cargo publish --dry-run

# Gerçekten yayımla
cargo publish

# Belirli bir paketi workspace'ten yayımla
cargo publish -p shared-types

Yayımlamadan önce Cargo.toml içinde description, license ve repository alanlarının dolu olması gerekir.

Sonuç

Cargo, Rust ekosisteminin en güçlü yanlarından biridir. Bir sysadmin veya DevOps mühendisi olarak Rust araçları geliştirirken Cargo’nun sunduğu altyapıyı iyi kullanmak, hem geliştirme sürecinizi hızlandırır hem de ürettiğiniz araçların kalitesini artırır.

Özellikle şu noktalara dikkat etmenizi öneririm: Workspace yapısını erken benimsemek büyük projelerde hayat kurtarır. cargo check komutunu cargo build yerine kullanmak geliştirme döngüsünü hızlandırır. CI/CD pipeline’larınıza cargo audit ve cargo clippy eklemek güvenlik ve kod kalitesi açısından kritiktir. Cross-compilation özelliğini kullanarak platformdan bağımsız, statik binary’ler üretebilirsiniz.

Rust ve Cargo ekosistemine yatırım yapmak başlangıçta öğrenme eğrisi nedeniyle zorlu gelebilir. Ancak derleme zamanında yakalanan hatalar, bellek güvenliği ve mükemmel performans göz önüne alındığında, sistem araçları geliştirmek için Rust ve Cargo ikilisi gerçekten kazandıran bir kombinasyon.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir