Go dilinde bir dilime eleman eklemek için yerleşik append fonksiyonu, elemanları bir dilimden diğerine aktarmak için yerleşik copy fonksiyonu kullanılır. append her zaman sonucu döndürür ve siz bu sonucu yeniden atamalısınız: s = append(s, x). Kapasite yetiyorsa eleman mevcut alttaki diziye yazılır, yetmiyorsa yeni ve daha büyük bir dizi ayrılır ve eski elemanlar oraya taşınır. copy(dst, src) ise iki dilimin uzunluklarından küçük olanı kadar eleman kopyalar ve kopyalanan sayıyı döndürür.
Kural kısa olsa da iki dilimin aynı diziyi paylaşması yüzünden şaşırtıcı sonuçlar çıkabilir. Bu yazıda kapasite büyümesini, ortak dizi tuzağını ve üç indeksli dilimle çözümünü, copy fonksiyonunun örtüşen dilimlerdeki davranışını, araya ekleme ve silme yöntemlerini ve bellek tutma sorununu Go 1.27.3 ile çalıştırılmış örneklerle ele alıyoruz. Dilimin ne olduğu, len ve cap kavramları için önce Go array ve slice farkı yazısını okumanızı öneririz. Serinin tamamı Go öğrenme rehberi sayfasında.
append nasıl çalışır?

Spesifikasyondaki imza şematik olarak append(s S, x ...E) S şeklindedir: S dilim tipi, E onun eleman tipidir. Fonksiyon sıfır ya da daha fazla değeri dilimin sonuna ekler ve aynı tipte bir dilim döndürür. Spesifikasyonun belirlediği davranış iki durumdan ibarettir: s diliminin kapasitesi yeni değerleri sığdırmaya yetmiyorsa append hem eski elemanları hem yenileri alacak büyüklükte yeni bir alttaki dizi ayırır; yetiyorsa mevcut diziyi yeniden kullanır.
Effective Go, append fonksiyonunun yerleşik olmasını, eleman tipi çağıran tarafından belirlenen bu fonksiyonun derleyici desteğine ihtiyaç duymasıyla açıklar; tek bir append her dilim tipiyle çalışır. Sonucun döndürülmesi şarttır, çünkü alttaki dizi değişmiş olabilir ve dilim başlığı (işaretçi, uzunluk, kapasite) değerle geçirilir. Derleyici, sonucu kullanılmayan bir append çağrısını kabul etmez:
package main
func main() {
s := []int{1}
append(s, 2)
}# command-line-arguments
./main.go:5:2: append(s, 2) (value of type []int) is not usedKapasite büyümesi: ne zaman yeni dizi ayrılır?
Aşağıdaki program boş bir dilime on eleman ekliyor ve kapasitenin değiştiği her adımı yazdırıyor. Kapasite değiştiğinde dilim artık farklı bir alttaki diziyi gösterir:
package main
import "fmt"
func main() {
var s []int
eskiCap := cap(s)
for i := range 10 {
s = append(s, i)
if cap(s) != eskiCap {
fmt.Printf("len=%d cap=%d (yeni dizi)\n", len(s), cap(s))
eskiCap = cap(s)
}
}
fmt.Println(s)
}
// Go 1.27.3 windows/amd64 ile alınan çıktı
// (büyüme adımları dil tarafından garanti edilmez):
// len=1 cap=1 (yeni dizi)
// len=2 cap=2 (yeni dizi)
// len=3 cap=4 (yeni dizi)
// len=5 cap=8 (yeni dizi)
// len=9 cap=16 (yeni dizi)
// [0 1 2 3 4 5 6 7 8 9]Bu çıktıdaki sayıları bir kural olarak ezberlemeyin. Spesifikasyon yalnızca "yeterince büyük" yeni bir dizi ayrılacağını söyler, büyüme oranını belirlemez; değerler Go sürümüne ve eleman tipine göre farklı olabilir. Programınız belirli bir kapasiteye güvenmemeli, yalnızca len ve cap değerlerinin gerçekte ne döndürdüğüne bakmalıdır.
Kaç eleman ekleyeceğinizi önceden biliyorsanız kapasiteyi baştan ayırmak tekrar tekrar yeni dizi ayrılmasını önler. Bunun için make([]T, 0, n) kullanabilir ya da mevcut bir dilim için slices.Grow(s, n) çağırabilirsiniz. slices paketinin belgesine göre Grow, en az n elemanın yeni ayırma olmadan eklenebileceğini garanti eder ve n negatifse panikler.
Birden fazla eleman, dilim ve string ekleme
append değişken sayıda argüman alır. Bir dilimin tamamını eklemek için çağrı noktasında ... ile dilimi argüman listesine açarsınız. Effective Go, ... olmadan bu çağrının derlenmeyeceğini, çünkü ikinci argümanın tipinin eleman tipi olmadığını belirtir. Spesifikasyon bir özel durum daha tanımlar: []byte tipine eklerken ikinci argüman olarak ... ile birlikte bir string verebilirsiniz; bu durumda stringin baytları eklenir.
package main
import (
"fmt"
"slices"
)
func main() {
a := []string{"elma", "armut"}
b := []string{"kiraz", "erik"}
a = append(a, b...) // bir dilimi açarak ekleme
fmt.Println(a)
var bayt []byte
bayt = append(bayt, "Go"...) // string baytlarını ekleme
fmt.Println(bayt)
// Araya ekleme ve silme
s := []int{1, 2, 4, 5}
s = slices.Insert(s, 2, 3)
fmt.Println(s)
s = slices.Delete(s, 0, 2)
fmt.Println(s, len(s))
// Elle silme: i indeksindeki elemanı çıkar
t := []int{10, 20, 30, 40}
i := 1
t = append(t[:i], t[i+1:]...)
fmt.Println(t)
}
// Çıktı:
// [elma armut kiraz erik]
// [71 111]
// [1 2 3 4 5]
// [3 4 5] 3
// [10 30 40]Örnekteki [71 111] çıktısı "Go" stringinin iki baytıdır. Aynı programın ikinci yarısı araya ekleme ve silme işlemlerini gösteriyor; bunlara aşağıda ayrıca değiniyoruz.
Ortak dizi tuzağı
append kapasite yettiğinde mevcut diziye yazdığı için, aynı tabandan türetilmiş iki dilim birbirinin verisini ezebilir. Bu, dilimlerle ilgili en sık karşılaşılan şaşırtıcı davranıştır:
package main
import "fmt"
func main() {
taban := make([]int, 3, 10)
a := append(taban, 1)
b := append(taban, 2) // aynı alttaki dizinin 3. konumuna yazar
fmt.Println(a, b)
fmt.Println(len(taban), cap(taban))
}
// Çıktı:
// [0 0 0 2] [0 0 0 2]
// 3 10taban diliminin uzunluğu 3, kapasitesi 10 olarak ayarlandı. a := append(taban, 1) kapasite yettiği için 1 değerini alttaki dizinin 3. konumuna yazar. b := append(taban, 2) de aynı başlıktan başlar ve aynı konuma 2 yazar. Sonuçta a ile b aynı diziyi gösteren, aynı içeriğe sahip iki dilim olur ve a[3] değeri artık 1 değildir. taban değişkeninin kendisi değişmez; uzunluğu hâlâ üçtür.
Bu tuzak gerçek kodda genellikle şu biçimde ortaya çıkar: bir fonksiyon kendisine verilen dilime append yapıp sonucu saklar, çağıran da aynı dilime başka bir şey ekler. Kapasite fazlası varsa iki taraf aynı belleğe yazar.
Üç indeksli dilimle kapasiteyi sınırlama
Tam dilimleme ifadesi a[low:high:max] elde edilen dilimin kapasitesini max - low olarak ayarlar. Kapasiteyi uzunluğa eşitlediğinizde bir sonraki append mevcut diziye yazamaz ve yeni dizi ayırmak zorunda kalır:
package main
import "fmt"
func main() {
taban := make([]int, 3, 10)
kirpik := taban[0:3:3] // len 3, cap 3
a := append(kirpik, 1) // kapasite dolu: yeni dizi
b := append(kirpik, 2) // yine yeni dizi
fmt.Println(a, b)
fmt.Println(len(kirpik), cap(kirpik))
}
// Çıktı:
// [0 0 0 1] [0 0 0 2]
// 3 3Burada kirpik diliminin uzunluğu ve kapasitesi üçtür. Her iki append de yeni bir dizi ayırdığı için a ile b birbirinden bağımsızdır. Bir dilimi başka bir koda verirken o kodun sizin dizinize yazmasını istemiyorsanız s[:len(s):len(s)] biçimini kullanabilirsiniz. slices.Clip(s) fonksiyonu tam olarak bunu yapar; paket belgesine göre s[:len(s):len(s)] döndürür.
copy: kaç eleman kopyalanır?
copy(dst, src) elemanları kaynaktan hedefe kopyalar ve kopyalanan eleman sayısını döndürür. Bu sayı len(src) ile len(dst) değerlerinden küçük olanıdır; kapasite değil uzunluk dikkate alınır. İki argümanın eleman tipi aynı olmalıdır. Özel bir durum olarak hedef []byte ise kaynak bir string olabilir. Spesifikasyon, kaynak ile hedefin aynı belleği paylaştığı durumda da sonucun doğru olduğunu belirtir; bu yüzden copy bir dilim içinde elemanları kaydırmak için güvenle kullanılabilir.
package main
import "fmt"
func main() {
kaynak := []int{1, 2, 3, 4, 5}
hedef := make([]int, 3)
n := copy(hedef, kaynak)
fmt.Println(n, hedef)
n = copy(kaynak, kaynak[2:]) // örtüşen dilimler
fmt.Println(n, kaynak)
b := make([]byte, 5)
n = copy(b, "Merhaba")
fmt.Println(n, string(b))
var bos []int
fmt.Println(copy(bos, kaynak))
}
// Çıktı:
// 3 [1 2 3]
// 3 [3 4 5 4 5]
// 5 Merha
// 0Örnekteki son satır sık yapılan bir hatayı gösteriyor: var bos []int uzunluğu sıfır olan bir dilimdir, bu yüzden copy hiçbir şey kopyalamaz ve 0 döndürür. copy hedefi büyütmez; hedefin önceden make([]int, len(src)) gibi bir çağrıyla yeterli uzunlukta oluşturulması gerekir. Bağımsız bir kopya almanın kısa yolları slices.Clone(s) ve append([]T(nil), s...) biçimleridir. slices.Clone belgesine göre kopya sığdır (elemanlar atamayla kopyalanır) ve kaynağın nil olma durumunu korur.
Araya ekleme ve silme

Go dilinde dilimden eleman silmek ya da araya eleman eklemek için yerleşik bir operatör yoktur; bu işlemler append, copy veya slices paketiyle yapılır. Yukarıdaki örnekte gördüğünüz append(t[:i], t[i+1:]...) deyimi i konumundaki elemanı çıkarır: önce i öncesindeki kısım alınır, sonra sonrasındaki elemanlar aynı dizide bir konum sola kaydırılarak eklenir. Bu işlem alttaki diziyi yerinde değiştirir, yani orijinal dilimin içeriği de değişir.
Go 1.21 ile gelen slices paketi bu işlemleri adlandırılmış fonksiyonlarla sunar. slices.Insert(s, i, v...) değerleri i konumuna ekler ve i değeri len(s) değerinden büyükse panikler. slices.Delete(s, i, j) s[i:j] aralığını çıkarır; belgeye göre işlem O(len(s)-i) maliyetindedir ve dilimin sonunda boşalan elemanları sıfırlar. Çok sayıda eleman silinecekse belge, bunları tek tek değil tek bir çağrıyla silmeyi önerir. Paketin diğer fonksiyonları için Go slices, maps ve sort paketleri yazısına bakın.
| İşlem | Deyim | Not |
|---|---|---|
| Sona ekleme | s = append(s, x) | Kapasite yetmezse yeni dizi |
| Dilim ekleme | s = append(s, t...) | t aynı tipte dilim olmalı |
| Bağımsız kopya | c := slices.Clone(s) | Sığ kopya, nil durumu korunur |
| Hedefe kopyalama | n := copy(dst, src) | min(len(dst), len(src)) eleman |
| i konumunu silme | s = slices.Delete(s, i, i+1) | Yerinde değişir, sondaki eleman sıfırlanır |
| Araya ekleme | s = slices.Insert(s, i, v) | i > len(s) ise panik |
| Kapasiteyi kırpma | s = s[:len(s):len(s)] | Sonraki append yeni dizi ayırır |
| Önceden yer ayırma | s := make([]T, 0, n) | n elemana kadar yeni ayırma yok |
make ile uzunluk ve kapasite karıştırmak
make([]int, 3) çağrısı uzunluğu 3 olan, yani zaten üç sıfır içeren bir dilim oluşturur. Bu dilime append yaptığınızda yeni elemanlar dördüncü konumdan itibaren eklenir. Amacınız boş bir dilimle başlayıp yer ayırmaksa uzunluğu sıfır verin:
package main
import "fmt"
func main() {
s := make([]int, 3) // len 3: üç sıfır zaten içeride
s = append(s, 1, 2)
fmt.Println(s)
d := make([]int, 0, 3) // len 0, cap 3
d = append(d, 1, 2)
fmt.Println(d)
}
// Çıktı:
// [0 0 0 1 2]
// [1 2]Kural basit: dilimi indeksle dolduracaksanız (s[i] = v) uzunluğu verin; append ile dolduracaksanız uzunluğu sıfır, kapasiteyi beklenen eleman sayısı yapın.
Bellek tutma sorunu
Go Blog'daki "Go Slices: usage and internals" yazısı şu durumu anlatır: bir dilimi yeniden dilimlemek alttaki diziyi kopyalamaz, dolayısıyla küçük bir parça bile tüm diziyi bellekte tutar. Dilim yaşadığı sürece çöp toplayıcı diziyi serbest bırakamaz. Büyük bir dosyayı okuyup içinden birkaç bayt döndüren bir fonksiyon, dosyanın tamamını bellekte tutmaya devam edebilir. Çözüm, ihtiyacınız olan kısmı yeni bir dilime kopyalamaktır:
package main
import (
"fmt"
"slices"
)
func main() {
buyuk := make([]byte, 1<<20) // 1 MiB
copy(buyuk, "başlık")
parca := buyuk[:3] // 1 MiB'lik diziyi canlı tutar
kopya := slices.Clone(buyuk[:3]) // yalnızca 3 bayt
fmt.Println(len(parca), cap(parca))
fmt.Println(len(kopya), cap(kopya) < 1<<20)
onceden := make([]int, 0, 100) // kapasiteyi önceden ayır
for i := range 100 {
onceden = append(onceden, i)
}
fmt.Println(len(onceden), cap(onceden))
g := slices.Grow([]int{1}, 50)
fmt.Println(len(g), cap(g) >= 51)
}
// Çıktı:
// 3 1048576
// 3 true
// 100 100
// 1 trueparca yalnızca 3 elemanlı görünse de kapasitesi 1 MiB kadardır ve bu dizinin tamamını canlı tutar. slices.Clone ile alınan kopya ise yalnızca gereken baytları içeren küçük bir diziye işaret eder. Örneğin ikinci yarısı önceden kapasite ayırmanın etkisini gösteriyor: 100 elemanlık kapasiteyle başlayan dilime 100 append yapıldığında kapasite değişmez, yani yeni dizi ayrılmaz.
Eleman işaretçileri ve range değişkenleri
Bir dilim elemanının adresini aldıktan sonra append yeni bir dizi ayırırsa, eski işaretçi artık eski diziyi gösterir. Bu işaretçi üzerinden yapılan yazma yeni dilimde görünmez. Benzer biçimde for _, v := range s döngüsündeki v elemanın kopyasıdır; ona yapılan atama dilimi değiştirmez:
package main
import "fmt"
func main() {
notlar := []int{40, 75, 90}
ilk := ¬lar[0]
notlar = append(notlar, 100) // kapasite dolu: yeni dizi
*ilk = 0
fmt.Println(notlar[0], *ilk)
// Döngüde eleman değiştirmek için indeks kullanın
for i := range notlar {
notlar[i] += 5
}
for _, v := range notlar {
v *= 2 // yalnızca kopya değişir
}
fmt.Println(notlar)
}
// Çıktı:
// 40 0
// [45 80 95 105]Kapasitesi 3 olan dilime dördüncü eleman eklendiğinde yeni dizi ayrıldığı için *ilk = 0 ataması yalnızca eski diziyi etkiler ve notlar[0] hâlâ 40 değerini taşır. Elemanları döngüde değiştirmek istediğinizde indeksle (notlar[i]) yazın. Döngü değişkenlerinin her yinelemede yeni oluşturulması (Go 1.22) gibi ayrıntılar Go for döngüsü ve range yazısında anlatılıyor.
Sık yapılan hatalar

- Sonucu atamamak:
append(s, x)tek başına derlenmez;s = append(s, x)yazın. Sonucu başka bir değişkene atamak da ortak dizi tuzağına yol açabilir. - Aynı tabandan iki append:
a := append(t, 1)veb := append(t, 2)fazla kapasite varsa aynı belleğe yazar. Bağımsızlık gerekiyorsa üç indeksli dilim ya daslices.Clonekullanın. - Uzunluğu sıfır hedefe copy:
copyhedefi büyütmez, kopyalanan eleman sayısı hedefin uzunluğuyla sınırlıdır. - make ile len vermek ve append yapmak:
make([]int, n)sonrasıappendyeni elemanlarınsıfırın arkasına ekler. - Kapasite değerlerine güvenmek: büyüme oranı dil tarafından garanti edilmez; kodunuz belirli bir
capdeğerine dayanmamalıdır. - Büyük diziden küçük parça döndürmek: alt dilim tüm diziyi canlı tutar; uzun ömürlü küçük parçaları kopyalayın.
- append sonrası eski eleman işaretçisini kullanmak: yeni dizi ayrıldıysa işaretçi eski belleği gösterir.
Derleyici ve çalışma zamanı hata mesajlarının toplu listesi için Go sık karşılaşılan hatalar yazısına bakabilirsiniz.
Sık Sorulan Sorular
append neden yeni dilim döndürüyor?
Dilim başlığı değerle geçirildiği için append çağıranın değişkenindeki uzunluğu değiştiremez. Ayrıca kapasite yetmediğinde tamamen farklı bir dizi ayrılır. Güncel uzunluğu ve yeni diziyi taşıyan başlık ancak dönüş değeriyle size ulaşabilir.
Bir dilimin bağımsız kopyasını nasıl alırım?
slices.Clone(s) en kısa yoldur. Alternatif olarak c := make([]T, len(s)) ve copy(c, s) yazabilirsiniz. İki yöntem de sığ kopyadır: elemanlar işaretçi, map ya da dilim içeriyorsa onların gösterdiği veri paylaşılmaya devam eder.
copy örtüşen dilimlerde güvenli mi?
Evet. Spesifikasyon, append ve copy için sonucun argümanların belleği paylaşıp paylaşmamasından bağımsız olduğunu belirtir. copy(s, s[2:]) gibi bir çağrı elemanları doğru şekilde sola kaydırır.
Dilimin kapasitesini nasıl küçültürüm?
Yalnızca görünür kapasiteyi sınırlamak için s[:len(s):len(s)] veya slices.Clip(s) kullanın; bu alttaki diziyi küçültmez. Belleği gerçekten serbest bırakmak istiyorsanız elemanları yeni, küçük bir dilime kopyalayın ve eski dilime referans bırakmayın.
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: Go öğrenme rehberi · Go array ve slice farkı · Go slices, maps ve sort paketleri · Go map kullanımı · Go pointer (işaretçi) · Go sık karşılaşılan hatalar