Rust'ta slice (dilim), bir koleksiyonun ardışık bir bölümüne verilen referanstır: metin için &str, diğer tipler için &[T] yazılır ve &s[0..5] gibi aralık sözdizimiyle oluşturulur. Slice bir referans türü olduğu için sahiplik taşımaz; bellekte yalnızca başlangıç adresini ve uzunluğu tutar, bu yüzden kopyalamadan ucuza parça göstermenin standart yoludur.
Bu yazıda slice'ın hangi sorunu çözdüğünü, aralık sözdiziminin kısaltmalarını, &str parametrelerin neden daha esnek olduğunu, UTF-8 karakter sınırlarında panik almamak için nelere dikkat etmek gerektiğini ve &[T] üzerindeki sık kullanılan metotları çalışan örneklerle anlatıyoruz. Referans ve ödünç alma kurallarını bilmiyorsanız önce Rust referans ve borrowing yazısını okuyun; slice da aynı kurallara tabidir.
Slice hangi sorunu çözer?
The Rust Book konuyu basit bir görevle açar: boşluklarla ayrılmış bir metnin ilk kelimesini döndüren bir fonksiyon yazmak. Slice olmadan akla gelen yol, kelimenin bittiği bayt indeksini usize olarak döndürmektir. Ancak bu sayı metnin o anki hâline bağlı, ondan kopuk bir değerdir. Metin değişirse sayı hâlâ yerinde durur ve artık anlamsızdır; derleyicinin bunu fark etmesinin bir yolu yoktur:
fn ilk_kelime_sonu(s: &String) -> usize {
let baytlar = s.as_bytes();
for (i, &b) in baytlar.iter().enumerate() {
if b == b' ' {
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("merhaba dünya");
let son = ilk_kelime_sonu(&s);
s.clear(); // metin boşaldı
println!("son = {son}, metin = {s:?}");
}
// çıktı: son = 7, metin = ""Program sorunsuz derlenir ve çalışır, ama son değişkeni artık boş bir metne ait bir konumu gösteriyor. İkinci kelimeyi de döndürmek isteseydik başlangıç ve bitiş olmak üzere iki ayrı sayıyı metinle senkron tutmak zorunda kalırdık. Slice bu sorunu kökten çözer: dönen değer, verinin kendisine bağlı bir referanstır ve borrow checker onu metnin geçerliliğine bağlar.
String slice sözdizimi
![String slice sözdizimi: &s[2..5], &s[..5], &s[3..], &s[..]](/assets/blog/rust-slice-ve-str-dilimleri-b2.webp)
Bir String'in bir bölümüne referans almak için köşeli parantez içinde aralık yazılır: [baslangic..bitis]. Başlangıç dilimin ilk konumu, bitiş ise son konumun bir fazlasıdır. Slice içeride başlangıç konumunu ve bitis - baslangic kadar uzunluğu saklar. Aralığın iki ucu da isteğe bağlıdır:
fn main() {
let s = String::from("merhaba dünya");
let bas = &s[0..7];
let son = &s[8..]; // sona kadar
let ilk2 = &s[..2]; // baştan itibaren
let tum = &s[..]; // tamamı
println!("{bas} | {son} | {ilk2} | {tum}");
println!("{} {}", s.len(), son.len());
}
// çıktı: merhaba | dünya | me | merhaba dünya
// çıktı: 14 6Dikkat ederseniz "merhaba dünya" on üç karakter olduğu hâlde len() 14 döndürür; "dünya" da beş karakter ama 6 bayttır. Bunun nedeni ü harfinin UTF-8'de iki bayt kaplamasıdır. String slice indeksleri karakter değil bayt konumudur. Bu ayrıntı aşağıda UTF-8 bölümünde önemli hâle gelecek.
| Yazım | Anlamı | Eşdeğeri |
|---|---|---|
| &s[2..5] | 2, 3 ve 4 numaralı baytlar | - |
| &s[..5] | Baştan 5. bayta kadar | &s[0..5] |
| &s[3..] | 3. bayttan sona kadar | &s[3..s.len()] |
| &s[..] | Metnin tamamı | &s[0..s.len()] |
| &s[2..2] | Boş slice | Hata vermez |
Slice döndüren fonksiyon ve derleyici koruması
Aynı görevi slice ile yazdığımızda fonksiyon, ilk boşluğa kadar olan bölümün referansını döndürür. Artık dönen değer metne bağlıdır. Metni temizlemeye çalışırsanız clear değiştirilebilir bir ödünç gerektirdiği, kelime ise hâlâ okuma referansı olarak kullanıldığı için derleyici kodu reddeder:
fn ilk_kelime(s: &str) -> &str {
let baytlar = s.as_bytes();
for (i, &b) in baytlar.iter().enumerate() {
if b == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("merhaba dünya");
let kelime = ilk_kelime(&s);
s.clear();
println!("ilk kelime: {kelime}");
}
// error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutableÖnceki örnekte çalışma anında sessizce oluşan mantık hatası, şimdi derleme anında yakalanıyor. The Book bu kazancı, bütün bir hata sınıfının derleme zamanında ortadan kalkması olarak özetler. clear satırını println! sonrasına aldığınızda kod derlenir, çünkü okuma referansının son kullanımı artık temizlemeden öncedir.
Neden &String yerine &str alınmalı?
Yukarıdaki fonksiyonun parametresini &String değil &str yaptık. Bunun nedeni esnekliktir: &str alan bir fonksiyon hem bir String'in tamamını veya bir parçasını, hem de string literal'leri kabul eder. &String değerinin &str beklenen yere verilebilmesi deref coercion denen dönüşüm sayesindedir; ayrıntısı Box ve Deref konusunda anlatılıyor.
fn ilk_kelime(s: &str) -> &str {
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
fn main() {
let sahipli = String::from("merhaba dünya");
let literal = "selam millet";
println!("{}", ilk_kelime(&sahipli)); // &String otomatik &str olur
println!("{}", ilk_kelime(&sahipli[8..])); // String'in bir parçası
println!("{}", ilk_kelime(literal)); // literal zaten &str
}
// çıktı: merhaba
// çıktı: dünya
// çıktı: selamString literal'lerin tipinin &str olması da slice kavramıyla açıklanır: literal, çalıştırılabilir dosyanın içinde duran metne işaret eden bir slice'tır. Standart kütüphane belgesine göre literal'lerin tipi tam olarak &'static str'dir, yani program boyunca geçerlidir. Literal'lerin değiştirilemez olmasının nedeni de budur: &str değiştirilemez bir referanstır. 'static için bkz. Rust lifetime rehberi.
UTF-8 sınırları: Türkçe metinde panik almamak

String slice aralıkları geçerli UTF-8 karakter sınırlarında başlayıp bitmelidir. Çok baytlı bir karakterin ortasından dilim almaya çalışırsanız program derlenir ama çalışma anında panikler. Türkçe metinlerde ç, ğ, ı, ö, ş, ü gibi harfler iki bayt tuttuğu için bu hata kolayca ortaya çıkar:
fn main() {
let s = String::from("çay");
println!("{}", s.len());
let _parca = &s[0..1];
}
// çıktı: 4
// panik: end byte index 1 is not a char boundary; it is inside 'ç' (bytes 0..2 of string)Panik mesajı neyin yanlış gittiğini açıkça söyler: 1. bayt ç harfinin içindedir ve bu harf 0..2 baytlarını kaplar. Kullanıcıdan gelen ya da önceden bilinmeyen metinlerde panik yerine kontrollü sonuç almak için str belgesindeki üç araç işinize yarar:
fn main() {
let s = "çay";
println!("{:?}", s.get(0..1)); // sınırda değil: None
println!("{:?}", s.get(0..2)); // ç tam olarak 0..2
println!("{}", s.is_char_boundary(1));
for (i, c) in s.char_indices() {
println!("{i}: {c}");
}
}
// çıktı: None
// çıktı: Some("ç")
// çıktı: false
// çıktı: 0: ç
// çıktı: 2: a
// çıktı: 3: yget(aralik): indekslemenin panik yapmayan karşılığıdır; aralık sınıra denk gelmiyorsa veya taşıyorsaNonedöner.is_char_boundary(i): verilen bayt konumunun bir karakterin ilk baytı ya da metnin sonu olup olmadığını söyler. Metnin uzunluğundan büyük indekstefalsedöner.char_indices(): her karakteri başladığı bayt konumuyla birlikte verir; güvenli kesme noktalarını bulmanın en kolay yoludur.
Karakter sayısı, chars ve bytes farkı gibi metne özgü konular Rust String ve &str farkı yazısında daha ayrıntılı ele alınıyor.
Dizi ve vektör slice: &[T]
Slice yalnızca metne özgü değildir. Bir dizinin ya da Vec'in bir bölümüne de aynı sözdizimiyle referans alabilirsiniz; tipi &[T] olur. Parametreyi &[i32] olarak tanımlayan bir fonksiyon dizi, vektör ve bunların parçalarıyla çalışır. Değiştirilebilir slice &mut [T] ise gösterdiği elemanları yerinde değiştirmenize izin verir:
fn toplam(sayilar: &[i32]) -> i32 {
let mut t = 0;
for s in sayilar {
t += s;
}
t
}
fn ikiye_katla(parca: &mut [i32]) {
for x in parca.iter_mut() {
*x *= 2;
}
}
fn main() {
let dizi = [1, 2, 3, 4, 5];
let mut v = vec![10, 20, 30];
println!("{}", toplam(&dizi));
println!("{}", toplam(&dizi[1..3]));
println!("{}", toplam(&v));
ikiye_katla(&mut v[1..]);
println!("{v:?}");
println!("{:?}", &dizi[5..]); // sondan sonrası: boş slice
}
// çıktı: 15
// çıktı: 5
// çıktı: 60
// çıktı: [10, 40, 60]
// çıktı: []Son satır ilginçtir: &dizi[5..] uzunluğa eşit bir başlangıçla boş slice üretir ve hata vermez. Standart kütüphane belgesi, len()..len() dahil boş alt aralıklara izin verildiğini, ama alt sınırı uzunluktan büyük aralıkların yasak olduğunu belirtir. Sınırı aşan bir aralık çalışma anında panikler:
fn main() {
let v = vec![1, 2, 3];
let _d = &v[1..5];
}
// panik: range end index 5 out of range for slice of length 3Bir bölümü yerinde değiştirmek
Değiştirilebilir slice'ın pratik bir faydası, koleksiyonun yalnızca bir kısmı üzerinde işlem yapabilmektir. sort ve reverse gibi metotlar &mut self alan slice metotlarıdır; bu yüzden onları bir aralık üzerinde çağırdığınızda yalnızca o aralıktaki elemanlar etkilenir, geri kalanı olduğu gibi kalır:
fn main() {
let mut v = vec![5, 3, 9, 1, 7];
v[..3].sort(); // yalnızca ilk üç eleman sıralanır
println!("{v:?}");
v[3..].reverse(); // yalnızca son iki eleman ters çevrilir
println!("{v:?}");
}
// çıktı: [3, 5, 9, 1, 7]
// çıktı: [3, 5, 9, 7, 1]Bu yaklaşım ek bellek ayırmaz ve kopya üretmez: işlem doğrudan vektörün kendi belleği üzerinde yapılır. Bir yardımcı fonksiyonun yalnızca belirli bir bölgeye dokunmasını istiyorsanız ona &mut v[a..b] vermek, fonksiyonun bu bölgenin dışına erişememesini de tip düzeyinde garanti eder.
Slice üzerinde sık kullanılan metotlar

Vec ve diziler, slice'a ait metotların tamamını kullanabilir. Aşağıdaki tablo günlük işlerde en sık gereken birkaçını özetler; hepsi standart kütüphanenin slice belgesinde tanımlıdır.
| Metot | Dönüş | Davranış |
|---|---|---|
| len() | usize | Eleman sayısı |
| first() / last() | Option<&T> | Boş slice için None |
| get(i) / get(a..b) | Option<&T> veya Option<&[T]> | Sınır dışında panik yerine None |
| split_at(mid) | (&[T], &[T]) | mid > len ise panik |
| contains(&x) | bool | T: PartialEq gerekir |
| windows(n) | Iterator | Örtüşen n elemanlı pencereler, n = 0 ise panik |
| chunks(n) | Iterator | Örtüşmeyen parçalar, son parça kısa olabilir |
| to_vec() | Vec<T> | Elemanları klonlayarak sahipli kopya üretir |
fn main() {
let v = [3, 8, 1, 9, 4];
println!("{:?} {:?}", v.first(), v.get(10));
let (sol, sag) = v.split_at(2);
println!("{sol:?} {sag:?}");
println!("{}", v.contains(&9));
let pencereler: Vec<&[i32]> = v.windows(2).collect();
let parcalar: Vec<&[i32]> = v.chunks(2).collect();
println!("{pencereler:?}");
println!("{parcalar:?}");
}
// çıktı: Some(3) None
// çıktı: [3, 8] [1, 9, 4]
// çıktı: true
// çıktı: [[3, 8], [8, 1], [1, 9], [9, 4]]
// çıktı: [[3, 8], [1, 9], [4]]windows ile chunks arasındaki fark, pencerelerin üst üste binmesidir: ardışık elemanları karşılaştırmak için windows, veriyi sabit büyüklükte gruplara bölmek için chunks kullanılır. Bu metotların döndürdüğü yineleyicilerle çalışmak için Rust iterator rehberi yazısına bakabilirsiniz.
Bellekte slice: işaretçi ve uzunluk
Slice'ın boyutu derleme zamanında bilinmez; bu yüzden [T] ve str tiplerini doğrudan değişkene koyamazsınız, her zaman bir referans ya da Box gibi bir işaretçi arkasında kullanırsınız. Bu referanslar, adresin yanında uzunluğu da taşıdığı için normal bir referansın iki katı büyüklüktedir. 64 bit Windows'ta çalıştırdığımız örnek bunu gösteriyor:
fn main() {
println!("&u8: {} bayt", size_of::<&u8>());
println!("&[u8]: {} bayt", size_of::<&[u8]>());
println!("&str: {} bayt", size_of::<&str>());
}
// çıktı: &u8: 8 bayt
// çıktı: &[u8]: 16 bayt
// çıktı: &str: 16 baytStandart kütüphane belgesi bu ilişkiyi platformdan bağımsız biçimde, slice referansının boyutunun işaretçi boyutunun iki katı olduğu şeklinde ifade eder. 32 bit bir hedefte sayılar 4 ve 8 olur.
Ne zaman slice, ne zaman sahipli tip?
Slice kullanıp kullanmamaya karar verirken temel soru, verinin kimin elinde kalacağıdır. Bir fonksiyon yalnızca okuyacak ya da girdisinin bir bölümünü geri gösterecekse &str veya &[T] almak ve döndürmek en ucuz yoldur; çağıran taraf verinin sahibi olmaya devam eder. Fonksiyon yeni bir metin ya da liste üretiyorsa, örneğin iki metni birleştiriyor veya bir listeyi filtreleyip yeni bir liste kuruyorsa, sonucu String veya Vec<T> olarak döndürmek gerekir. Çünkü üretilen veri fonksiyonun içinde oluşur ve ona referans döndürmek sarkan referans olurdu.
Bir struct içinde veri saklarken de aynı ayrım geçerlidir. Alan olarak &str tutmak mümkündür ama struct'ın, gösterdiği metinden daha uzun yaşamayacağını lifetime parametresiyle belirtmeniz gerekir. Çoğu uygulama kodunda sahipli tipler saklanır; slice ise fonksiyon sınırlarında, geçici görünümler için kullanılır.
Sık yapılan hatalar
- Karakter sayısını bayt sanmak:
&s[0..3]ilk üç karakteri değil ilk üç baytı ister. Türkçe metinde bu panik demektir;char_indicesveyagetkullanın. - Slice tutarken kaynağı değiştirmek: Bir
Vec'ten aldığınız slice canlıykenpushveyaclearçağırmak E0502 verir. Slice'ı kullanma işini bitirdikten sonra kaynağı değiştirin. - Parametrede
&Vec<T>kullanmak: Okuyan fonksiyonlar için&[T]daha geneldir; diziler ve alt aralıklar da kabul edilir. - Sınır kontrolünü unutmak: Kullanıcı girdisine bağlı indekslerde
v[a..b]yerinev.get(a..b)panik riskini ortadan kaldırır. - Slice'ı fonksiyondan bağımsız saklamaya çalışmak: Bir struct alanında
&strtutmak lifetime notasyonu gerektirir; sahipli veri istiyorsanızStringveyaVec<T>saklayın.
Sık Sorulan Sorular
&str ile String arasındaki temel fark nedir?
String heap'teki metnin sahibidir ve büyüyebilir. &str ise bir metnin tamamına veya bir bölümüne bakan, sahip olmayan bir referanstır. Metni değiştirip saklamak için String, yalnızca okumak için &str kullanılır.
Slice kopyalama yapar mı?
Hayır. Slice oluşturmak yalnızca bir adres ve uzunluk üretir; elemanlar yerinde kalır. Sahipli bir kopya istediğinizde to_vec() veya metin için to_string() çağırırsınız.
Slice ile desen eşleştirme yapılabilir mi?
Evet. [ilk, .., son] veya [bas, kalan @ ..] gibi slice desenleri vardır ve match içinde boş, tek elemanlı ve çok elemanlı durumları ayırmanızı sağlar. Ayrıntılar Rust pattern sözdizimi yazısında.
&mut [T] ile eleman ekleyebilir miyim?
Hayır. Değiştirilebilir slice, gösterdiği elemanların değerini değiştirmenize izin verir ama uzunluğu sabittir; push veya remove gibi metotlar slice'ta değil Vec'te tanımlıdır. Eleman eklemesi gereken bir fonksiyon &mut Vec<T> almalıdır.
Bir slice'ı iki değiştirilebilir parçaya nasıl bölerim?
split_at_mut kullanın. Aynı slice'tan iki ayrı &mut almak borrow kurallarına takılır; bu metot çakışmayan iki &mut [T] döndürerek sorunu güvenle çözer.
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 · Referans ve borrowing · String ve &str farkı · Vec kullanımı · Lifetime · Pattern sözdizimi · Rust nedir?