Rust'ta Hata Yönetimi 2026: Result, Option, thiserror ve anyhow

Result, Option, ? operatörü ve thiserror ile anyhow kütüphanelerini kullanarak Rust'ta hata yönetimi için kapsamlı rehber. Üretim uygulamalarında kullanılan en iyi pratikler ve kalıplar.

Rust'ta Result, Option, thiserror ve anyhow ile hata yönetimi

Rust'ta hata yönetimi, derleme zamanında başarı, başarısızlık ve değer yokluğunun açıkça ele alınmasını zorunlu kılan iki enum üzerine kuruludur: Result<T, E> ve Option<T>. Görünmez şekilde yayılan istisnalardan farklı olarak, Rust'ın yaklaşımı hata yollarını fonksiyon imzalarında görünür kılarak çalışma zamanı sürprizlerinin tüm kategorilerini ortadan kaldırır. ? operatörü, thiserror ve anyhow gibi kütüphanelerle birlikte bu açık modeli netliği kaybetmeden basitleştirir.

Hangi Tipi Ne Zaman Kullanmalı

Option<T> yasal olarak mevcut olmayabilecek değerler için kullanılır (yapılandırma alanları, arama sonuçları). Result<T, E> ise anlamlı hata bilgisiyle başarısız olabilecek işlemler için kullanılır (dosya I/O, ağ istekleri, ayrıştırma).

Result ve Option Temelleri

Result<T, E> başarıyı (Ok(T)) veya başarısızlığı (Err(E)) temsil eder. Option<T> bir değerin varlığını (Some(T)) veya yokluğunu (None) temsil eder. Her ikisi de toplam türlerdir—derleyici her varyantın ele alınmasını sağlar.

error_examples.rsrust
// Demonstrates Result and Option basic patterns

fn find_user(id: u64) -> Option<String> {
    // Returns None if user doesn't exist
    if id == 0 {
        None
    } else {
        Some(format!("User-{}", id))
    }
}

fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
    // Returns Err if parsing fails
    s.parse::<u16>()
}

fn main() {
    // Option handling - must address None case
    match find_user(42) {
        Some(name) => println!("Found: {}", name),
        None => println!("User not found"),
    }

    // Result handling - must address Err case
    match parse_port("8080") {
        Ok(port) => println!("Port: {}", port),
        Err(e) => println!("Invalid port: {}", e),
    }
}

Derleyici, bu dönüş değerlerini açıkça ele almadan görmezden gelen kodu reddeder. Bu tasarım, diğer dillerde null pointer istisnaları veya yakalanmamış hatalar olarak ortaya çıkacak hataları derleme zamanında yakalar.

Özlü Yayılım için Soru İşareti Operatörü

? operatörü, uzun match zincirlerini okunabilir doğrusal koda dönüştürür. Bir Result üzerine uygulandığında, hata mevcutsa erken dönüş yapar veya başarı değerini açar. Aynı durum Option için de geçerlidir.

file_reader.rsrust
// Using ? for clean error propagation

use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn read_first_line(path: &str) -> Result<String, io::Error> {
    let file = File::open(path)?;  // Returns early if open fails
    let mut reader = BufReader::new(file);
    let mut line = String::new();
    reader.read_line(&mut line)?;  // Returns early if read fails
    Ok(line.trim().to_string())
}

fn get_port_from_config(path: &str) -> Result<u16, Box<dyn std::error::Error>> {
    let content = read_first_line(path)?;
    let port = content.parse::<u16>()?;  // ParseIntError converts via From
    Ok(port)
}

? operatörü, hata türünün From trait'i aracılığıyla fonksiyonun dönüş hata türüne dönüştürülebilir olmasını gerektirir. Yukarıda gösterildiği gibi Box<dyn std::error::Error> kullanmak, standart Error trait'ini uygulayan herhangi bir hata türünü kabul eder.

thiserror ile Özel Hatalar Oluşturma

thiserror kütüphanesi, özel hata türleri için standart kod yazma ihtiyacını ortadan kaldırır. Prosedürel bir makro aracılığıyla Error, Display ve From implementasyonlarını türetir.

errors.rsrust
// Custom error types using thiserror 2.0

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
    #[error("configuration file not found at {path}")]
    NotFound { path: String },

    #[error("invalid port number: {0}")]
    InvalidPort(#[from] std::num::ParseIntError),

    #[error("IO error reading config")]
    IoError(#[from] std::io::Error),

    #[error("missing required field: {0}")]
    MissingField(String),
}

fn load_config(path: &str) -> Result<Config, ConfigError> {
    let content = std::fs::read_to_string(path)?;  // IoError auto-converts
    let port: u16 = content
        .lines()
        .find(|l| l.starts_with("port="))
        .ok_or(ConfigError::MissingField("port".into()))?
        .strip_prefix("port=")
        .unwrap()
        .parse()?;  // ParseIntError auto-converts to InvalidPort

    Ok(Config { port })
}

struct Config {
    port: u16,
}

#[from] özelliği otomatik dönüşümler oluşturarak farklı kaynak hata türleriyle ? operatörünün sorunsuz kullanımını sağlar. Hata mesajları #[error(...)] format dizeleri aracılığıyla kendi kendini belgeler hale gelir.

anyhow ile Uygulama Düzeyinde Hatalar

thiserror belirli hata türleriyle kütüphane koduna uygun olsa da, anyhow tür ayrıntısından çok hata bağlamının önemli olduğu uygulamaları hedefler. Context trait'i herhangi bir hataya açıklayıcı mesajlar ekler.

main.rsrust
// Application error handling with anyhow 1.0

use anyhow::{Context, Result, bail, ensure};

fn load_database_url() -> Result<String> {
    std::env::var("DATABASE_URL")
        .context("DATABASE_URL environment variable not set")
}

fn connect_to_database(url: &str) -> Result<DatabaseConnection> {
    ensure!(!url.is_empty(), "database URL cannot be empty");

    let conn = DatabaseConnection::new(url)
        .context("failed to establish database connection")?;

    if !conn.is_healthy() {
        bail!("database connection unhealthy after establishment");
    }

    Ok(conn)
}

fn main() -> Result<()> {
    let url = load_database_url()?;
    let conn = connect_to_database(&url)
        .context("application startup failed")?;

    // Context chains create readable error traces:
    // Error: application startup failed
    // Caused by:
    //     0: failed to establish database connection
    //     1: connection refused

    Ok(())
}

struct DatabaseConnection;
impl DatabaseConnection {
    fn new(_url: &str) -> Result<Self> { Ok(Self) }
    fn is_healthy(&self) -> bool { true }
}

context() metodu hataları ek bilgilerle sarmalayarak hata ayıklamaya yardımcı bir zincir oluşturur. bail! makrosu biçimlendirilmiş bir hata mesajıyla erken çıkış sağlarken, ensure! panik yapmak yerine hata döndüren bir doğrulama görevi görür.

Rust mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Gerçek Projelerde thiserror ve anyhow'u Birleştirme

Kütüphaneler, tüketiciler tarafından programatik işleme için thiserror aracılığıyla yapılandırılmış hatalar sunar. Uygulamalar bu hataları insan tarafından okunabilir çıktı için anyhow ile sarmalar. Bu ayrım, hata ayıklama yeteneğini korurken API'leri temiz tutar.

lib.rs - Library code with thiserrorrust
// Exposes typed errors for programmatic handling

use thiserror::Error;

#[derive(Error, Debug)]
pub enum PaymentError {
    #[error("insufficient funds: required {required}, available {available}")]
    InsufficientFunds { required: u64, available: u64 },

    #[error("card declined: {reason}")]
    CardDeclined { reason: String },

    #[error("payment provider unavailable")]
    ProviderUnavailable(#[source] reqwest::Error),
}

pub fn process_payment(amount: u64) -> Result<Receipt, PaymentError> {
    // Library returns specific, matchable error types
    Err(PaymentError::InsufficientFunds {
        required: amount,
        available: 50,
    })
}

pub struct Receipt;
main.rs - Application code with anyhowrust
// Wraps library errors with context

use anyhow::{Context, Result};
use my_payment_lib::{process_payment, PaymentError};

fn checkout(cart_total: u64) -> Result<()> {
    match process_payment(cart_total) {
        Ok(_receipt) => Ok(()),
        Err(PaymentError::InsufficientFunds { required, available }) => {
            // Handle specific case differently
            println!("Add {} to your balance", required - available);
            Ok(())
        }
        Err(e) => Err(e).context("checkout payment processing failed"),
    }
}

Bu kalıp, kurtarma mümkün olduğunda çağıranların belirli varyantları eşleştirmesini sağlarken, başarısızlıkları yukarı doğru yayarken zengin hata bağlamından da yararlanır. Rust topluluğu bu yaklaşımı büyük ölçüde standartlaştırmıştır, bu konu Rust API kılavuzlarında tartışılmaktadır.

Asenkron Kod için Hata Yönetimi Kalıpları

Asenkron fonksiyonlar tıpkı senkron olanlar gibi Result döndürür. ? operatörü async blokları içinde aynı şekilde çalışır ve hem thiserror hem de anyhow değişiklik olmadan entegre olur.

async_errors.rsrust
// Error handling in async Rust with Tokio

use anyhow::{Context, Result};
use std::time::Duration;

async fn fetch_user_data(user_id: u64) -> Result<UserData> {
    let response = reqwest::get(format!("https://api.example.com/users/{}", user_id))
        .await
        .context("HTTP request to user API failed")?;

    let status = response.status();
    if !status.is_success() {
        anyhow::bail!("user API returned status {}", status);
    }

    let data: UserData = response
        .json()
        .await
        .context("failed to parse user data JSON")?;

    Ok(data)
}

async fn fetch_with_retry(user_id: u64, attempts: u32) -> Result<UserData> {
    let mut last_error = None;

    for attempt in 1..=attempts {
        match fetch_user_data(user_id).await {
            Ok(data) => return Ok(data),
            Err(e) => {
                last_error = Some(e);
                if attempt < attempts {
                    tokio::time::sleep(Duration::from_millis(100 * attempt as u64)).await;
                }
            }
        }
    }

    Err(last_error.unwrap()).context(format!("failed after {} attempts", attempts))
}

#[derive(serde::Deserialize)]
struct UserData {
    name: String,
}

Birden fazla async işlemi birleştirirken, bunları eşzamanlı çalıştırırken ilk hatayı yaymak için tokio veya futures'dan try_join! kullanılmalıdır.

Downcasting ve Hata İncelemesi

Hem anyhow::Error hem de Box<dyn Error>, orijinal hata türünü kurtarmak için downcasting'i destekler. Bu, genel hataları yayarken belirli ayrıntıları günlüğe kaydetmeyi mümkün kılar.

downcasting.rsrust
// Inspecting wrapped error types

use anyhow::{Context, Result};
use std::io;

fn log_and_propagate(result: Result<()>) -> Result<()> {
    if let Err(ref e) = result {
        // Check if the root cause is a specific type
        if let Some(io_err) = e.downcast_ref::<io::Error>() {
            match io_err.kind() {
                io::ErrorKind::NotFound => {
                    tracing::warn!("file not found, using defaults");
                }
                io::ErrorKind::PermissionDenied => {
                    tracing::error!("permission denied - check file ownership");
                }
                _ => {
                    tracing::error!("IO error: {:?}", io_err);
                }
            }
        }
    }
    result
}

Downcasting, genel hata yönetimi ile belirli kurtarma mantığı arasında köprü kurar. Dikkatli kullanılmalıdır—sık downcasting gerçekleşiyorsa, türlendirilmiş bir hata enum'unun daha iyi hizmet edip edemeyeceği değerlendirilmelidir.

Option ve Result Arasında Dönüştürme

Standart kütüphane, Option ve Result arasında dönüştürme için metotlar sağlayarak farklı API'ler farklı kalıplar kullandığında sorunsuz kompozisyon sağlar.

conversions.rsrust
// Option and Result interoperability

fn get_env_port() -> Option<u16> {
    std::env::var("PORT")
        .ok()  // Result -> Option (discards error)
        .and_then(|s| s.parse().ok())
}

fn get_env_port_with_error() -> Result<u16, String> {
    std::env::var("PORT")
        .map_err(|_| "PORT not set".to_string())?
        .parse()
        .map_err(|_| "PORT is not a valid number".to_string())
}

fn lookup_and_parse(map: &std::collections::HashMap<String, String>, key: &str) -> Result<u16, String> {
    map.get(key)
        .ok_or_else(|| format!("key '{}' not found", key))?  // Option -> Result
        .parse()
        .map_err(|e| format!("parse error for '{}': {}", key, e))
}

ok() metodu yalnızca varlığın önemli olduğu durumlarda hata ayrıntılarını atar. ok_or() ve ok_or_else() metotları None'u özel bir hataya dönüştürerek Option değerlerinden ? yayılımını mümkün kılar.

Performans Değerlendirmeleri

Rust'ta hata yönetimi, başarı yolunda çalışma zamanı maliyeti taşımaz. Result ve Option, deterministik boyutta yığın üzerinde tahsis edilen enum'lardır. Derleyici, bir dalın erişilemez olduğunu kanıtlayabildiğinde kontrolleri optimize eder.

| Yaklaşım | Başarı Yolu Maliyeti | Hata Yolu Maliyeti | |----------|------------------|-------------------| | Result/Option | Sıfır | Yığın çözme (ucuz) | | panic! | Sıfır | Tam yığın çözme + temizlik | | C++ istisnaları | Sıfır (genellikle) | Pahalı heap tahsisi + RTTI |

Kütüphane kodunda unwrap() ve expect() kullanımından kaçınılmalıdır—bunlar yalnızca başarısızlığın gerçekten bir hata gösterdiği durumlar için ayrılmalıdır. Hataların yaygın olduğu performans kritik yollarda, heap üzerinde tahsis edilen hata türleri yerine satır içi verili enum'lar düşünülmelidir.

Sonuç

  • Result<T, E> kurtarılabilir başarısızlıkları ele alır; Option<T> yokluğu ele alır—her ikisi de derleme zamanı işlemeyi zorlar
  • ? operatörü hataları özlü bir şekilde yayar, tür dönüşümü için From trait implementasyonları gerektirir
  • thiserror kütüphaneler için sıfır standart kodla yapılandırılmış hata türleri oluşturur
  • anyhow uygulamalar için context(), bail! ve ensure! destekleyen bağlamsal hata zincirleri sağlar
  • Her ikisi birleştirilmelidir: kütüphaneler thiserror aracılığıyla türlendirilmiş hatalar sunar, uygulamalar bunları anyhow ile sarmalar
  • Async kod aynı kalıpları kullanır—? async bloklarda değişiklik olmadan çalışır
  • OptionResult'a dönüştürmek için ok_or() kullanılır; tersi için ok() kullanılır
  • Sarmalanmış hatalar yalnızca belirli kurtarma mantığı orijinal türü gerektirdiğinde downcast edilmelidir

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

#rust
#error-handling
#best-practices
#thiserror
#anyhow

Paylaş

İlgili makaleler