Rust'ta enum, bir değerin birkaç olası varyanttan yalnızca biri olabileceğini ifade eden tiptir ve her varyant kendi verisini taşıyabilir. Standart kütüphanedeki Option<T> da sıradan bir enum'dur: Some(T) bir değerin var olduğunu, None olmadığını söyler. Rust'ta null yoktur; değerin eksik olabileceği her yer tipte Option olarak görünür ve derleyici None durumunu ele almanızı ister.
Bu yazıda enum tanımlamayı, veri taşıyan varyantları, enum'lara metot eklemeyi, ayırıcı (discriminant) değerlerini, Option'ın null'a göre neden daha güvenli olduğunu ve günlük kodda en sık kullanılan Option metotlarını çalışan örneklerle inceliyoruz. Enum değerlerini match ile işlemenin ayrıntıları Rust match ve if let yazısında, hata döndürmek için kullanılan Result ise Rust Result ve hata yönetimi yazısında.
Enum tanımlamak ve varyantlar
Struct'lar alanları bir araya getirir, enum'lar ise bir değerin olası seçeneklerinden birini temsil eder. The Book'un IP adresi örneği bunu iyi gösterir: bir adres ya dördüncü sürüm ya da altıncı sürümdür, ikisi birden olamaz. Enum'un en güçlü yanı, her varyantın farklı tipte ve miktarda veri taşıyabilmesidir; aynı şeyi struct ile yapmak için ayrı bir tür alanı ve her durum için ayrı veri alanları gerekirdi.
enum IpAdres {
V4(u8, u8, u8, u8),
V6(String),
}
fn yaz(ip: &IpAdres) -> String {
match ip {
IpAdres::V4(a, b, c, d) => format!("{a}.{b}.{c}.{d}"),
IpAdres::V6(s) => s.clone(),
}
}
enum Mesaj {
Cikis,
Tasi { x: i32, y: i32 },
Yaz(String),
RenkDegistir(i32, i32, i32),
}
impl Mesaj {
fn aciklama(&self) -> String {
match self {
Mesaj::Cikis => String::from("çıkış"),
Mesaj::Tasi { x, y } => format!("taşı: {x},{y}"),
Mesaj::Yaz(metin) => format!("yaz: {metin}"),
Mesaj::RenkDegistir(r, g, b) => format!("renk: {r} {g} {b}"),
}
}
}
fn main() {
let ev = IpAdres::V4(127, 0, 0, 1);
let geri = IpAdres::V6(String::from("::1"));
println!("{} {}", yaz(&ev), yaz(&geri));
let mesajlar = [
Mesaj::Cikis,
Mesaj::Tasi { x: 3, y: 4 },
Mesaj::Yaz(String::from("merhaba")),
Mesaj::RenkDegistir(0, 160, 255),
];
for m in &mesajlar {
println!("{}", m.aciklama());
}
}
// çıktı: 127.0.0.1 ::1
// çıktı: çıkış
// çıktı: taşı: 3,4
// çıktı: yaz: merhaba
// çıktı: renk: 0 160 255Mesaj enum'unda dört farklı varyant biçimi bir arada: veri taşımayan Cikis, struct gibi adlandırılmış alanları olan Tasi, tek bir String taşıyan Yaz ve üç sayı taşıyan RenkDegistir. Bunları dört ayrı struct ile modelleseydiniz her biri ayrı bir tip olurdu ve hepsini kabul eden tek bir fonksiyon yazmak zorlaşırdı. Enum ise tek bir tiptir; impl bloğuyla struct'lardaki gibi metot da ekleyebilirsiniz.
Standart kütüphane de IP adresleri için aynı fikri kullanır: std::net::IpAddr varyantlarının içinde Ipv4Addr ve Ipv6Addr adlı struct'lar taşır. Kendi IpAdres tipinizi tanımlamanız buna engel olmaz, çünkü standart tanımı kapsamınıza almadıkça ad çakışması yaşanmaz.
Enum mı, struct mı?

İkisi de kendi tipinizi tanımlamanın yoludur ama farklı soruları yanıtlar. Struct "bu değerin şu alanların hepsi vardır" der; enum ise "bu değer şu seçeneklerden tam olarak biridir" der. Bir siparişin müşteri adı, tutarı ve tarihi aynı anda bulunur, bu yüzden struct uygundur. Siparişin durumu ise beklemede, kargoda ya da teslim edildi durumlarından yalnızca biridir ve her durumun ayrı verisi olabilir; burada enum doğal seçimdir. İkisi sık sık birlikte kullanılır: bir struct alanı enum olabilir, bir enum varyantı da struct taşıyabilir.
| Soru | Struct | Enum |
|---|---|---|
| Değer ne içerir? | Tüm alanları birlikte | Varyantlardan yalnızca birini |
| Yeni seçenek eklemek | Yeni alan eklenir | Yeni varyant eklenir, match ifadeleri güncellenmeli |
| Veriye erişim | Alan adıyla: p.x | Desen eşleştirmeyle: match, if let |
| Metot tanımlama | impl bloğu | impl bloğu |
Enum'a yeni bir varyant eklediğinizde, bu enum üzerinde kapsayıcı match yazan her yerde derleyici eksik durumu bildirir. Bu, büyüyen kod tabanlarında unutulan durumları bulmanın güvenilir bir yoludur.
Varyant adı bir kurucu fonksiyondur
The Book'un vurguladığı ayrıntı şudur: veri taşıyan her varyantın adı, aynı zamanda o varyantı oluşturan bir fonksiyon gibi davranır. IpAdres::V6 bir String alıp IpAdres döndüren bir fonksiyondur. Bu yüzden varyant adını doğrudan map gibi fonksiyon bekleyen yerlere verebilirsiniz:
fn main() {
let sayilar = vec![1, 2, 3];
let secenekler: Vec<Option<i32>> = sayilar.into_iter().map(Some).collect();
println!("{secenekler:?}");
}
// çıktı: [Some(1), Some(2), Some(3)]Ayırıcı (discriminant) değerleri
Rust Reference'a göre her enum değerinin, hangi varyantı tuttuğunu belirleyen bir tamsayı ayırıcısı vardır. Hiçbir varyantı veri taşımayan (unit-only) enum'larda bu değeri açıkça verebilir ve as ile sayıya dönüştürebilirsiniz. Açıkça verilmeyen ayırıcı bir önceki varyantınkinin bir fazlasıdır; ilk varyant belirtilmemişse sıfırdan başlar.
#[derive(Debug)]
enum Gun {
Pazartesi = 1,
Sali,
Carsamba,
Pazar = 7,
}
fn main() {
println!("{}", Gun::Pazartesi as i32);
println!("{}", Gun::Sali as i32);
println!("{}", Gun::Carsamba as i32);
println!("{:?} = {}", Gun::Pazar, Gun::Pazar as u8);
}
// çıktı: 1
// çıktı: 2
// çıktı: 3
// çıktı: Pazar = 7Veri taşıyan varyantları olan bir enum'a açık ayırıcı vermek ise ancak #[repr(u8)] gibi ilkel bir temsil seçildiğinde mümkündür. Günlük kodda varyantlara sayısal anlam yüklemek gerekmedikçe ayırıcıyla uğraşmanız gerekmez.
Option<T>: null yerine tip
Pek çok dilde her değişken ya bir değer ya da null olabilir; null'ı gerçek bir değer gibi kullanmaya çalışmak da çok yaygın bir hata kaynağıdır. The Book, null'ın mucidi Tony Hoare'un onu "milyar dolarlık hatam" olarak nitelediği 2009 konuşmasını aktarır. Rust kavramı reddetmez, uygulamayı değiştirir: değerin olmayabileceği durum Option<T> ile ifade edilir. Bu enum o kadar sık kullanılır ki prelude'a dahildir; Some ve None'ı Option:: öneki olmadan yazabilirsiniz.
Asıl güvenlik, Option<T> ile T'nin farklı tipler olmasından gelir. Derleyici bir Option<i8> değerini kesin var olan bir i8 gibi kullanmanıza izin vermez:
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let toplam = x + y;
}
// error[E0277]: cannot add `Option<i8>` to `i8`Sonuç olarak tipi Option olmayan her değerin null olmadığını güvenle varsayabilirsiniz. Tipi Option olan bir değeri kullanmadan önce ise None durumunu açıkça ele almak zorundasınız. None yazdığınız yerde derleyici iç tipi çıkaramazsa tipi belirtmeniz gerekir: let yok: Option<i32> = None;.
fn bol(pay: f64, payda: f64) -> Option<f64> {
if payda == 0.0 { None } else { Some(pay / payda) }
}
fn main() {
for payda in [4.0, 0.0] {
match bol(10.0, payda) {
Some(sonuc) => println!("sonuç: {sonuc}"),
None => println!("sıfıra bölünemez"),
}
}
}
// çıktı: sonuç: 2.5
// çıktı: sıfıra bölünemezDeğeri çıkarmak: unwrap, expect ve güvenli alternatifler

Standart kütüphane belgesi, Some içindeki değeri çıkaran metotları None durumunda ne yaptıklarına göre gruplar. unwrap ve expect panikler; geri kalanlar bir varsayılan üretir:
| Metot | None olduğunda |
|---|---|
| expect(mesaj) | Verilen mesajla panikler |
| unwrap() | Genel bir mesajla panikler |
| unwrap_or(v) | Verilen v değerini döndürür |
| unwrap_or_else(f) | f fonksiyonunu çalıştırıp sonucunu döndürür |
| unwrap_or_default() | T tipinin Default değerini döndürür |
| unwrap_unchecked() | Tanımsız davranış; unsafe gerektirir |
fn main() {
let yok: Option<i32> = None;
println!("{}", yok.unwrap_or(0));
println!("{}", yok.unwrap_or_else(|| 2 * 21));
println!("{}", yok.unwrap_or_default());
let ad: Option<String> = None;
println!("{:?}", ad.unwrap_or_default());
}
// çıktı: 0
// çıktı: 42
// çıktı: 0
// çıktı: ""unwrap_or argümanını her durumda hesaplar; varsayılan değer pahalı bir işlemle üretiliyorsa yalnızca gerektiğinde çalışan unwrap_or_else tercih edilir. unwrap çağrısı None üzerinde çalışırsa program şu mesajla panikler:
fn main() {
let yok: Option<i32> = None;
let _x = yok.unwrap();
}
// panik: called `Option::unwrap()` on a `None` valueexpect aynı işi yapar ama panik mesajını siz belirlersiniz; yok.expect("ayar bulunamadı") çağrısında panik satırı doğrudan ayar bulunamadı olur. Hata ayıklarken hangi varsayımın bozulduğunu görmek için unwrap yerine anlamlı bir mesajla expect yazmak iyi bir alışkanlıktır. Panik ile kurtarılabilir hata ayrımı için Result ve hata yönetimi yazısına bakın.
Dönüştürme ve zincirleme metotları
Option üzerinde çalışırken her seferinde match yazmak yerine, belgedeki dönüştürme metotlarıyla işlemleri zincirleyebilirsiniz. Bu metotlar None değerini değiştirmeden aktarır, yalnızca Some içindeki değeri işler:
fn main() {
let metin = Some("42");
let sayi: Option<i32> = metin.and_then(|s| s.parse().ok());
println!("{sayi:?}");
let iki_kati = sayi.map(|n| n * 2);
println!("{iki_kati:?}");
let tek_mi = sayi.filter(|n| n % 2 == 1);
println!("{tek_mi:?}");
println!("{}", sayi.is_some_and(|n| n > 40));
println!("{}", sayi.map_or(0, |n| n + 1));
let sonuc: Result<i32, &str> = None.ok_or("değer yok");
println!("{sonuc:?}");
}
// çıktı: Some(42)
// çıktı: Some(84)
// çıktı: None
// çıktı: true
// çıktı: 43
// çıktı: Err("değer yok")map:Option<T>değeriniOption<U>değerine çevirir; fonksiyon yalnızcaSomeiçinde çalışır.and_then: Fonksiyonun kendisiOptiondöndürüyorsa kullanılır; iç içeOption<Option<U>>oluşmasını önler.filter: Koşulu sağlamayanSomedeğeriniNoneyapar.is_some_and: Değer varsa ve koşulu sağlıyorsatruedöner;Noneiçin fonksiyon hiç çalışmaz.map_or:Noneiçin varsayılanı,Someiçin fonksiyonun sonucunu döndürür.ok_or:OptiondeğeriniResult'a çevirir;Noneverilen hata değeri olur.
Option üzerinde yineleme
Belgeye göre Option, en fazla bir eleman üreten bir yineleyici gibi de kullanılabilir: Some(v) tek bir değer, None hiç değer üretmez. chain gibi başka bir yineleyici bekleyen metotlar Option değerini doğrudan kabul eder. Bu, koşullu olarak bir eleman eklemek istediğinizde kullanışlıdır:
fn main() {
let ek = Some(42);
let yok: Option<i32> = None;
let a: Vec<i32> = (1..3).chain(ek).collect();
let b: Vec<i32> = (1..3).chain(yok).collect();
println!("{a:?} {b:?}");
}
// çıktı: [1, 2, 42] [1, 2]? operatörü ve Option toplama
Option döndüren bir fonksiyonun içinde ? operatörünü kullanabilirsiniz: ifade Some ise içindeki değeri verir, None ise fonksiyon hemen None döndürür. Belgedeki örnek, bir yığından iki değer çekip toplamayı tek satıra indirir. Ayrıca Option değerlerinden oluşan bir yineleyiciyi collect ile tek bir Option<Vec<T>> değerine dönüştürebilirsiniz; elemanlardan biri bile None ise sonuç None olur.
fn son_iki_toplam(yigin: &mut Vec<i32>) -> Option<i32> {
Some(yigin.pop()? + yigin.pop()?)
}
fn main() {
let mut y = vec![1, 2, 3];
println!("{:?}", son_iki_toplam(&mut y));
println!("{:?}", son_iki_toplam(&mut y));
let tam: Option<Vec<i32>> = [Some(2), Some(4)].into_iter().collect();
let eksik: Option<Vec<i32>> = [Some(2), None].into_iter().collect();
println!("{tam:?} {eksik:?}");
println!("{}", None < Some(0));
}
// çıktı: Some(5)
// çıktı: None
// çıktı: Some([2, 4]) None
// çıktı: trueİkinci çağrıda yığında tek eleman kaldığı için ikinci pop None döndürür ve fonksiyon erken çıkar. Son satır da belgede tanımlı bir sıralama kuralını gösterir: T karşılaştırılabiliyorsa None her Some değerinden küçük sayılır.
Option içeriğini yerinde değiştirmek ve referanslarla çalışmak
Bir Option alanının içindeki değeri dışarı almak, yerine yenisini koymak ya da yoksa oluşturmak için belgede ayrı bir metot grubu vardır. Ayrıca as_ref ve as_deref, sahipliği taşımadan içerideki değere referans almanızı sağlar; bu, özellikle struct alanlarındaki Option<String> değerlerini okurken işe yarar:
fn main() {
let mut onbellek: Option<String> = None;
let deger = onbellek.get_or_insert_with(|| String::from("hesaplandı"));
deger.push('!');
println!("{onbellek:?}");
let ad: Option<&str> = onbellek.as_deref(); // Option<String> değil Option<&str>
println!("{ad:?}");
let eski = onbellek.replace(String::from("yeni"));
println!("{eski:?} {onbellek:?}");
let alinan = onbellek.take(); // yerine None bırakır
println!("{alinan:?} {onbellek:?}");
}
// çıktı: Some("hesaplandı!")
// çıktı: Some("hesaplandı!")
// çıktı: Some("hesaplandı!") Some("yeni")
// çıktı: Some("yeni") Nonetake, sahiplik yazısında anlattığımız std::mem::take'in Option'a özel karşılığıdır: değeri taşır ve geride geçerli bir None bırakır. Ayrıntı için Rust ownership kuralları yazısına bakabilirsiniz.
Option ek bellek harcar mı?
Standart kütüphane, belirli tipler için Option<T>'ın T ile aynı boyutta olmasını garanti eder. Bu tipler arasında Box<U>, &U, &mut U, fonksiyon işaretçileri ve NonZero sayı tipleri vardır. Bunlar hiçbir zaman sıfır olamadığı için sıfır değeri None olarak kullanılır; buna null pointer optimization denir:
fn main() {
println!("{}", size_of::<Box<i32>>() == size_of::<Option<Box<i32>>>());
println!("{}", size_of::<&u8>() == size_of::<Option<&u8>>());
}
// çıktı: true
// çıktı: trueYani isteğe bağlı bir referans ya da kutu için Option kullanmanın bellek maliyeti yoktur. Listede olmayan tipler için böyle bir garanti verilmez.
Sık yapılan hatalar

- Her yerde unwrap kullanmak: Örnek kodda pratiktir ama gerçek programda
Nonegeldiğinde panik demektir.?,unwrap_orya damatchkullanın. - Option<Option<T>> üretmek:
Optiondöndüren bir fonksiyonumapile çağırırsanız iç içe sonuç alırsınız;and_thenkullanın ya daflattenile bir seviyeyi kaldırın. - Alanı okurken taşımak:
&selfiçindenself.ad.unwrap()çağırmak değeri taşımaya çalışır ve derlenmez; önceas_ref()veyaas_deref()ile referansa çevirin. - None için tip belirtmemek: Tek başına
let x = None;derleyicinin iç tipi bilmesini engeller; tip belirtin ya da değişkeni sonradan birSomeile kullanın. - Varsayılanı gereksiz yere hesaplamak:
unwrap_or(pahali())fonksiyonu her zaman çalıştırır;unwrap_or_else(pahali)yalnızcaNonedurumunda çalıştırır.
Sık Sorulan Sorular
Rust enum ile C enum arasındaki fark nedir?
Rust'ta veri taşımayan bir enum C'deki gibi sayısal ayırıcılarla kullanılabilir. Fark, Rust enum varyantlarının veri de taşıyabilmesidir; her varyant farklı tipte ve sayıda alan içerebilir ve match tüm varyantları ele almanızı zorunlu kılar.
Option ne zaman, Result ne zaman kullanılır?
Değerin yokluğu normal bir durumsa ve nedenini anlatmanız gerekmiyorsa Option; işlemin neden başarısız olduğunu çağırana bildirmeniz gerekiyorsa Result kullanılır. İkisi arasında ok_or ve ok() ile dönüşüm yapılabilir.
Enum içinde başka bir enum taşıyabilir miyim?
Evet. Varyantlar string, sayı, struct ve başka enum dahil her tipte veri taşıyabilir. İç içe enum'ları tek bir match deseninde açmak için Rust pattern sözdizimi yazısına bakın.
Option değerini yazdırmak için ne gerekir?
{:?} biçimi için iç tipin Debug uygulaması yeterlidir; Option kendisi Display uygulamaz. Kendi enum'larınızda #[derive(Debug)] eklemek için derive rehberi yazısına bakabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Rust öğrenme rehberi · match ve if let · Pattern sözdizimi · Result ve hata yönetimi · Struct ve metotlar · Ownership · Rust nedir?