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.
