unsafe anahtar kelimesi Rust'ta beş ek işleme izin verir: ham işaretçinin gösterdiği değeri okumak, unsafe bir fonksiyon veya metodu çağırmak, değiştirilebilir bir static değişkene erişmek, unsafe bir trait'i uygulamak ve union alanlarını okumak. Bunun dışında hiçbir şey değişmez: unsafe blok borrow checker'ı kapatmaz, tip denetimini de devre dışı bırakmaz. Yalnızca derleyicinin doğrulayamadığı bu beş işlemin sorumluluğunu size devreder.
Bu yazıda bu beş yeteneği çalışan örneklerle inceliyoruz, ardından güvenli bir soyutlama kurmayı, tanımsız davranışın Rust'taki sınırlarını ve Rust 2024 edition'ın unsafe kodla ilgili getirdiği kuralları ele alıyoruz. Ownership ve borrow kurallarını henüz oturtmadıysanız önce ownership ve referans ve borrowing yazılarını okumanızı öneririz; unsafe kodun doğru olması tam olarak bu kuralları elle korumanıza bağlıdır.
unsafe neden var?

Kitap iki gerekçe verir. Birincisi, statik analiz doğası gereği tutucudur: derleyici bir programın kuralları koruduğundan emin olamadığında, geçersiz bir programı kabul etmektense geçerli bir programı reddetmeyi tercih eder. Kodunuzun doğru olduğunu bildiğiniz ama derleyicinin bunu kanıtlayamadığı durumlarda unsafe devreye girer. İkincisi, altta yatan donanım zaten güvensizdir. İşletim sistemiyle doğrudan konuşmak, başka dillerdeki kodu çağırmak ya da düşük seviyeli sistem programlaması yapmak için bu kapının açık olması gerekir.
Rustonomicon bu ayrımı güvenli Rust ve güvensiz Rust diye adlandırır. Güvenli Rust'ın temel özelliği, ne yaparsanız yapın tanımsız davranışa yol açamamasıdır. unsafe ise bu garantiyi sizin sağlamanızı ister. Kitabın önerisi açıktır: unsafe blokları küçük tutun; bellek hatası araştırırken sorunun bu bloklardan birinde olduğunu bilmek işinizi çok kolaylaştırır.
| Yetenek | Örnek | Neden derleyici denetleyemez? |
|---|---|---|
| Ham işaretçiyi dereference etmek | *p | İşaretçinin geçerli belleği gösterdiği bilinmez |
| unsafe fn çağırmak | slice::from_raw_parts_mut | Fonksiyonun ön koşullarını çağıran sağlar |
| static mut okumak veya yazmak | SAYAC += 1 | Eşzamanlı erişim veri yarışına yol açabilir |
| unsafe trait uygulamak | unsafe impl Send | Trait değişmezlerini derleyici doğrulayamaz |
| union alanı okumak | u.tam | O anda hangi alanın geçerli olduğu bilinmez |
Ham işaretçiler: *const T ve *mut T
Ham işaretçiler referanslara benzer ama referansların garantilerini taşımaz. Kitaba göre ham işaretçiler aynı konuma hem değiştirilemez hem değiştirilebilir işaretçi tutarak borrow kurallarını yok sayabilir, geçerli belleği gösterdikleri garanti edilmez, null olabilir ve otomatik temizlik yapmaz. Tip adındaki yıldız dereference operatörü değil, tip adının parçasıdır. Ham işaretçi oluşturmak güvenli koddur; tehlikeli olan yalnızca gösterdiği değere erişmektir.
fn main() {
let mut sayi = 5;
let r1 = &raw const sayi;
let r2 = &raw mut sayi;
unsafe {
*r2 += 10;
println!("r1: {}", *r1);
}
println!("boş mu: {}", r1.is_null());
let bos: *const i32 = std::ptr::null();
println!("null mu: {}", bos.is_null());
println!("as_ref: {:?}", unsafe { bos.as_ref() });
}
// çıktı:
// r1: 15
// boş mu: false
// null mu: true
// as_ref: None&raw const ve &raw mut ham ödünç alma operatörleridir ve doğrudan bir yerel değişkenden işaretçi ürettikleri için bu örnekteki işaretçiler geçerlidir. Aynı konuma hem okuma hem yazma işaretçisi tuttuğumuz halde derleme hatası almadık; referanslarla bu kod derlenmezdi. Kitap tam da bu yüzden dikkatli olunmasını ister, çünkü değiştirilebilir işaretçi üzerinden yazma bir veri yarışına kapı açabilir. Bir tamsayıyı as ile işaretçiye çevirip keyfi bir adres oluşturmak da mümkündür, ancak o adresi kullanmak tanımsız davranıştır. as_ref null işaretçide None döndürdüğü için null kontrolünü güvenli biçimde yapmanın bir yoludur, yine de unsafe içinde çağrılır çünkü null olmayan işaretçinin geçerli olduğunu varsayar.
unsafe fn ve unsafe blok
Bir fonksiyonun önüne unsafe yazmak, onu çağıranın yerine getirmesi gereken ve derleyicinin denetleyemediği koşullar olduğunu bildirir. Böyle bir fonksiyonu yalnızca unsafe blok içinden çağırabilirsiniz; blok, fonksiyonun belgesini okuduğunuzu ve sözleşmesine uyduğunuzu beyan etmenin yoludur.
/// # Safety
/// `p` geçerli ve hizalı bir `i32` göstermelidir.
unsafe fn oku(p: *const i32) -> i32 {
unsafe { *p }
}
fn main() {
let x = 42;
let deger = oku(&raw const x);
println!("{deger}");
}
// error[E0133]: call to unsafe function `oku` is unsafe and requires unsafe blockÇağrıyı let deger = unsafe { oku(&raw const x) }; biçiminde yazdığınızda kod derlenir ve 42 yazdırır. Fonksiyon gövdesindeki unsafe { *p } bloğuna da dikkat edin. Rust 2024 edition'da unsafe_op_in_unsafe_fn lint'i varsayılan olarak uyarı verir: unsafe fn gövdesindeki güvensiz işlemler de ayrıca bir unsafe blok içine alınmalıdır. Edition Guide bunun gerekçesini, unsafe kelimesinin iki rolü olmasıyla açıklar: fonksiyonu çağırmanın ön koşul taşıdığını bildirmek ve gövdede güvensiz işlemlere izin vermek. İkinci rol, açık blok olmadan fazla riskli bulunmuştur.
unsafe fn ilk<T>(x: &[T]) -> &T {
x.get_unchecked(0)
}
fn main() {
let v = [7, 8];
println!("{}", unsafe { ilk(&v) });
}
// warning[E0133]: call to unsafe function `core::slice::<impl [T]>::get_unchecked` is unsafe and requires unsafe block
// çıktı:
// 7Kod uyarıyla derlenir ve çalışır; uyarıyı gidermek için gövdeyi unsafe { x.get_unchecked(0) } olarak yazmanız yeterlidir. Belgelerde ve kitapta kullanılan bir gelenek de şudur: unsafe bir fonksiyonun belgesine # Safety başlıklı bir bölüm ekleyip çağıranın neyi sağlaması gerektiğini yazmak, her unsafe bloğun önüne de SAFETY ile başlayan bir yorumla kuralların nasıl korunduğunu açıklamak.
Güvenli soyutlama: split_at_mut örneği

Bir fonksiyonun içinde unsafe kod olması, fonksiyonun kendisinin unsafe olması gerektiği anlamına gelmez. Standart kütüphanenin büyük kısmı denetlenmiş güvensiz kodun üzerine kurulmuş güvenli soyutlamalardır. Kitabın örneği bir dilimi iki değiştirilebilir parçaya bölen split_at_mut fonksiyonudur. Yalnızca güvenli Rust ile yazmaya çalışınca derleyici reddeder:
fn ikiye_bol(degerler: &mut [i32], orta: usize) -> (&mut [i32], &mut [i32]) {
let uzunluk = degerler.len();
assert!(orta <= uzunluk);
(&mut degerler[..orta], &mut degerler[orta..])
}
// error[E0499]: cannot borrow `*degerler` as mutable more than once at a timeBorrow checker aynı dilimin örtüşmeyen iki parçasını ödünç aldığımızı anlayamaz; yalnızca aynı dilimden iki kez değiştirilebilir ödünç alındığını görür. Bu hata ve benzerleri E0499 ve E0502 yazısında ayrıntılı anlatılıyor. Kodun doğru olduğunu biz biliyoruz, derleyici bilmiyor; işte unsafe tam bu durum için var:
use std::slice;
fn ikiye_bol(degerler: &mut [i32], orta: usize) -> (&mut [i32], &mut [i32]) {
let uzunluk = degerler.len();
let ptr = degerler.as_mut_ptr();
assert!(orta <= uzunluk);
// SAFETY: orta <= uzunluk denetlendi; iki dilim örtüşmüyor ve
// ikisi de `degerler` ile aynı ayırmanın içinde kalıyor.
unsafe {
(
slice::from_raw_parts_mut(ptr, orta),
slice::from_raw_parts_mut(ptr.add(orta), uzunluk - orta),
)
}
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let (sol, sag) = ikiye_bol(&mut v, 2);
sol[0] = 100;
sag[0] = 300;
println!("{v:?}");
}
// çıktı:
// [100, 2, 300, 4, 5, 6]slice::from_raw_parts_mut bir ham işaretçi ve uzunluktan dilim oluşturur; işaretçinin geçerli olduğuna güvenmek zorunda olduğu için unsafe bir fonksiyondur. add metodu da kaydırılmış konumun geçerli olduğuna güvenir. assert! satırı sayesinde bloğa giren her işaretçinin dilimin içinde kaldığını biliyoruz, bu yüzden ikiye_bol fonksiyonunu unsafe işaretlemeye gerek yok ve güvenli koddan çağrılabilir. Gerçek projede elbette standart kütüphanedeki hazır v.split_at_mut(2) çağrısını kullanırsınız; aynı sonucu verir.
Rustonomicon bu noktada önemli bir uyarı yapar: güvenlik yerel değildir. Sınır denetimi yapan küçük bir fonksiyonda < karşılaştırmasını <= yapmak, yani yalnızca güvenli koddaki tek bir karakteri değiştirmek, unsafe bloğu dokunulmamış olsa bile fonksiyonu sağlam (sound) olmaktan çıkarır. Yukarıdaki örnekte assert! satırını silmek de aynı etkiyi yapar. Bu yüzden unsafe kodu incelerken bloğun kendisine değil, onun dayandığı tüm durumu kuran güvenli koda da bakmalısınız.
extern ile başka dillerdeki kodu çağırmak
Başka bir dilde yazılmış fonksiyonları çağırmak için extern bloğu kullanılır; bu yabancı fonksiyon arayüzü (FFI) demektir. "C" ifadesi fonksiyonun kullandığı uygulama ikili arayüzünü (ABI) belirtir. Rust 2024 edition'da extern blokları unsafe extern olarak işaretlenmek zorundadır; işaretsiz bir blok error: extern blocks must be unsafe hatası verir. Edition Guide'a göre bu, imzaların doğru olmasının bloğu yazanın sorumluluğu olduğunu vurgular; yanlış bir imza tanımsız davranışa yol açabilir.
unsafe extern "C" {
safe fn abs(girdi: i32) -> i32;
fn strlen(s: *const std::ffi::c_char) -> usize;
}
fn main() {
println!("abs(-3) = {}", abs(-3));
let metin = c"merhaba";
// SAFETY: `metin` NUL ile biten geçerli bir C dizgesi.
let n = unsafe { strlen(metin.as_ptr()) };
println!("strlen = {n}");
}
// çıktı:
// abs(-3) = 3
// strlen = 7Blok içindeki öğeler varsayılan olarak unsafe kabul edilir. C'nin abs fonksiyonu her i32 için güvenle çağrılabildiğinden onu safe ile işaretledik ve unsafe blok olmadan çağırdık. Kitap burada önemli bir hatırlatma yapar: safe yazmak fonksiyonu güvenli yapmaz, bu Rust'a verdiğiniz bir sözdür. strlen ise geçerli bir işaretçi istediği için unsafe kaldı. Ters yönde, Rust fonksiyonlarını başka dillere açmak için pub extern "C" fn tanımlayıp ad karıştırmayı (mangling) #[unsafe(no_mangle)] ile kapatırsınız. 2024 edition'da no_mangle, export_name ve link_section özniteliklerinin unsafe(...) içinde yazılması zorunludur; aksi halde error: unsafe attribute used without unsafe hatası alınır. Gerekçe, karıştırılmamış sembol adlarının tüm bağlanan kütüphanelerde ortak bir ad alanında çakışabilmesidir.
static mut ve daha güvenli alternatifler
Rust'ta küresel değişkenler static olarak tanımlanır. Değiştirilemez bir static okumak güvenlidir, ama static mut değişkene her erişim unsafe gerektirir, çünkü birden fazla iş parçacığı aynı anda erişirse veri yarışı oluşur. Rust 2024 edition'da bir adım daha atıldı: static_mut_refs lint'i varsayılan olarak deny düzeyinde ve static mut değişkene referans oluşturmayı reddediyor. Bu referans görünmez biçimde de oluşabilir; örneğin println! argümanı referans aldığı için şu kod derlenmez:
static mut SAYAC: u32 = 0;
fn main() {
unsafe {
SAYAC += 1;
println!("{}", SAYAC);
}
}
// error: creating a shared reference to mutable staticEdition Guide'a göre böyle bir referansı kurallara aykırı biçimde oluşturmak, hiç okunmasa bile anında tanımsız davranıştır. Önerilen yollar, iç değiştirilebilirlik sağlayan bir tipi değiştirilemez static içinde kullanmak (örneğin atomik tipler) ya da gerçekten küresel akıl yürütme gerekiyorsa ham işaretçiyle erişmektir:
use std::sync::atomic::{AtomicU32, Ordering};
static mut SAYAC: u32 = 0;
static ATOMIK: AtomicU32 = AtomicU32::new(0);
fn main() {
unsafe {
// SAFETY: tek iş parçacıklı main içinde, başka erişim yok.
SAYAC += 1;
println!("SAYAC: {}", *(&raw const SAYAC));
}
ATOMIK.fetch_add(5, Ordering::Relaxed);
println!("ATOMIK: {}", ATOMIK.load(Ordering::Relaxed));
}
// çıktı:
// SAYAC: 1
// ATOMIK: 5Atomik sürüm hiç unsafe gerektirmez ve iş parçacıkları arasında da doğrudur. Paylaşılan durum için atomikler, Mutex ve Arc seçenekleri Arc ve Mutex yazısında anlatılıyor.
unsafe trait ve union
Bir trait'in en az bir metodu derleyicinin doğrulayamadığı bir değişmez taşıyorsa trait unsafe trait olarak tanımlanır ve uygulaması da unsafe impl ile yazılır. En bilinen örnekler Send ve Sync işaretçi trait'leridir: derleyici bunları tamamen bu trait'leri uygulayan tiplerden oluşan tiplere otomatik uygular. Ham işaretçi gibi bu trait'leri uygulamayan bir alan içeren tipinizi yine de iş parçacıkları arasında güvenli ilan etmek istiyorsanız unsafe impl Send yazmanız ve gerekli denetimi kendiniz yapmanız gerekir.
union bir yapıya benzer, ama bir anda yalnızca bir alanı kullanılır ve tüm alanlar aynı belleği paylaşır. Kitaba göre esas kullanım alanı C kodundaki union'larla çalışmaktır. Alan okumak unsafe gerektirir, çünkü Rust o anda hangi alanın geçerli veriyi tuttuğunu bilemez:
#[repr(C)]
union Sayi {
tam: u32,
kesir: f32,
}
fn main() {
let s = Sayi { kesir: 1.0 };
// SAFETY: u32 ve f32 aynı boyutta; her bit deseni geçerli bir u32.
let bitler = unsafe { s.tam };
println!("{bitler:#x}");
println!("{:#x}", 1.0f32.to_bits());
}
// çıktı:
// 0x3f800000
// 0x3f800000İkinci satırdaki to_bits aynı sonucu tamamen güvenli kodla verir. Bu küçük örnek genel bir dersi de özetliyor: standart kütüphanede güvenli bir karşılık varsa onu tercih edin.
Tanımsız davranış: Rust neyi yasaklar, neyi yasaklamaz?

Rustonomicon: What Unsafe Rust Can Do sayfası, Rust çekirdek dilinin önlemeye çalıştığı tanımsız davranışları kısa bir listeyle verir. Bunlardan başlıcaları şunlardır:
- Sarkan (dangling) veya hizasız işaretçiyi dereference etmek.
- İşaretçi takma ad (aliasing) kurallarını çiğnemek.
- Fonksiyonu yanlış çağrı ABI'siyle çağırmak.
- Veri yarışına neden olmak.
- Geçersiz değer üretmek: 0 veya 1 dışında bir
bool, geçersiz ayırt ediciye sahip birenum, null fonksiyon işaretçisi, geçerli aralık dışında birchar, başlatılmamış bellekten okunan tamsayı, sarkan veya hizasız referans ya daBox.
Aynı sayfa, Rust'ın güvenli kabul ettiği ama büyük ihtimalle hatalı programlara işaret eden durumları da sayar: kilitlenme (deadlock), yarış durumu, bellek sızıntısı, yerleşik operatörlerle tamsayı taşması ve programın sonlandırılması. Yani güvenli Rust bu sorunları imkânsız kılmaz; yalnızca tanımsız davranış üretmelerini engeller. Daha ayrıntılı ve kesin liste için The Reference: Behavior considered undefined sayfasına bakın.
Sık yapılan hatalar
unsafebloğun borrow checker'ı kapattığını sanmak; referanslar blok içinde de denetlenir.- Bütün fonksiyonu tek bir büyük
unsafeblok içine almak; bloklar küçük ve gerekçeli olmalıdır. SAFETYyorumu ve# Safetybelgesi yazmadan güvensiz API yayımlamak.- Güvensiz bloğun dayandığı denetimi (örneğin bir
assert!) sonradan güvenli kodda kaldırmak. static mutdeğişkene referans almak; atomik tip veya&raw constkullanın.externbloğundaki imzayı C tarafındakiyle karşılaştırmadansafeolarak işaretlemek.- Standart kütüphanede güvenli karşılığı olan bir işi (
split_at_mut,to_bits) elleunsafekodla yazmak.
Sık Sorulan Sorular
unsafe kod yazmak kötü bir uygulama mı?
Hayır. Kitaba göre bu beş yetenekten birini bir gerekçeyle kullanmak yanlış ya da kınanacak bir şey değildir; yalnızca doğru yazmak daha zordur, çünkü derleyici bellek güvenliğini sizin yerinize koruyamaz. Açık unsafe işareti, sorun çıktığında kaynağını bulmayı kolaylaştırır.
Projemde hiç unsafe olmadığından nasıl emin olurum?
Crate kökünde #![forbid(unsafe_code)] özniteliği ya da Cargo.toml içinde [lints.rust] altında unsafe_code = "forbid" kullanabilirsiniz. Lint düzeyleri Clippy ve rustfmt yazısında ele alınıyor.
unsafe kullanınca program daha mı hızlı çalışır?
Kendiliğinden hayır. unsafe yalnızca derleyicinin doğrulayamadığı işlemlere izin verir. Kitap ham işaretçilerin bazı durumlarda garantilerden vazgeçip daha fazla performans ya da başka dillerle ve donanımla etkileşim imkânı verdiğini söyler, ama bunun bir ödünleşim olduğunu ve sorumluluğun size geçtiğini unutmayın.
Rust 2024 geçişinde unsafe ile ilgili neler değişti?
extern bloklarının unsafe extern olması, no_mangle gibi özniteliklerin unsafe(...) içinde yazılması, unsafe fn gövdesinde de unsafe blok istenmesi, static mut referanslarının reddedilmesi ve env::set_var ile env::remove_var fonksiyonlarının unsafe olması. Hepsi 2024 edition yazısında toplu halde anlatılıyor; set_var için ortam değişkenleri yazısına de 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 · Rust nedir? · Referans ve borrowing · Arc, Mutex ve atomikler · Rust 2024 edition · C++ nedir?