Go dilinde string, değiştirilemeyen (immutable) bir bayt dizisidir; len(s) karakter sayısını değil bayt sayısını verir ve s[i] bir byte döndürür. rune, bir Unicode kod noktasını temsil eden int32 takma adıdır; byte ise uint8 takma adıdır. Türkçe metinde "ş", "ğ", "ı" gibi harfler UTF-8 ile iki bayt tuttuğu için karakter bazlı işlemlerde for range döngüsünü, []rune dönüşümünü ya da unicode/utf8 paketini kullanmanız gerekir.
Bu yazıda stringlerin bayt düzeyindeki yapısını, rune ve byte farkını, UTF-8 çözümlemesini, geçersiz baytlarla karşılaşınca ne olduğunu, strings paketinin sık kullanılan fonksiyonlarını ve Türkçe büyük-küçük harf dönüşümünde unicode.TurkishCase kullanımını Go 1.27.3 ile çalıştırılmış örneklerle anlatıyoruz. Serinin tamamı için Go öğrenme rehberi sayfasına bakabilirsiniz.
String bir bayt dizisidir

Spesifikasyon string değerini, uzunluğu bayt sayısı olan ve oluşturulduktan sonra içeriği değiştirilemeyen bir bayt dizisi olarak tanımlar. Go Blog'un "Strings, bytes, runes and characters" yazısı bunu bir adım daha ileri götürür: string içinde herhangi baytlar bulunabilir; içeriğin Unicode ya da UTF-8 olması zorunlu değildir. Go'nun UTF-8'e özel davrandığı iki yer vardır: kaynak kodun kendisi UTF-8 olarak tanımlanmıştır, bu yüzden bayt kaçışları içermeyen string literalleri geçerli UTF-8 tutar; for range döngüsü de stringi UTF-8 olarak çözümler.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Işık"
fmt.Println(len(s), utf8.RuneCountInString(s))
fmt.Println(s[0], s[1]) // baytlar, karakter değil
fmt.Printf("% x\n", s)
var parcalar []string
for i, r := range s {
parcalar = append(parcalar, fmt.Sprintf("%d:%c(%U)", i, r, r))
}
fmt.Println(parcalar)
var baytlar []byte
for i := 0; i < len(s); i++ {
baytlar = append(baytlar, s[i])
}
fmt.Println(baytlar)
}
// Çıktı:
// 6 4
// 73 197
// 49 c5 9f c4 b1 6b
// [0:I(U+0049) 1:ş(U+015F) 3:ı(U+0131) 5:k(U+006B)]
// [73 197 159 196 177 107]"Işık" dört harften oluşur ama altı bayt tutar: "I" ve "k" ASCII olduğu için birer bayt, "ş" ile "ı" ikişer bayt kaplar. s[0] ve s[1] ifadeleri harf değil bayt döndürür; 197 değeri "ş" harfinin ilk baytıdır. %x fiilinin boşluk bayraklı biçimi (% x) baytları onaltılık olarak aralıklı yazdırır ve hata ayıklamada çok işe yarar. for range döngüsünde ise indeks, rune'un başladığı bayt konumudur: 0, 1, 3, 5. Klasik for i := 0; i < len(s); i++ döngüsü bayt bayt ilerler. utf8.RuneCountInString stringdeki rune sayısını verir.
Spesifikasyona göre len sonucu string sabitse derleme zamanı sabitidir. Bir string elemanının adresini alamazsınız: &s[i] geçersizdir. String karşılaştırması (==, <) bayt bazında sözlüksel yapılır; bu yüzden "Z" < "a" ve "İ" ile başlayan kelimeler ASCII harflerle başlayanların arkasına düşer.
rune ve byte: tipler ve dönüşümler
'ğ' gibi tek tırnaklı bir ifade rune sabitidir; tipi varsayılan olarak rune, değeri kod noktasıdır (ğ için 287, onaltılık 0x11F). []rune(s) dönüşümü stringi kod noktalarına ayırır, []byte(s) dönüşümü baytlarına. Go Blog, stringin alttaki dizisinin gizli olduğunu ve bu iki dönüşümün her birinin kopya oluşturduğunu belirtir; dönüşümden sonra bayt dilimi üzerinde yapılan değişiklik orijinal stringi etkilemez:
package main
import "fmt"
func ters(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
func main() {
s := "Gökyüzü"
r := []rune(s)
b := []byte(s)
fmt.Println(len(r), len(b))
fmt.Println(string(r[1]), string(r[:3]))
fmt.Println(ters(s))
b[0] = 'g' // kopya üzerinde değişiklik
fmt.Println(s, string(b))
var c rune = 'ğ'
var d byte = 'A'
fmt.Printf("%T %v %c | %T %v %c\n", c, c, c, d, d, d)
}
// Çıktı:
// 7 10
// ö Gök
// üzüyköG
// Gökyüzü gökyüzü
// int32 287 ğ | uint8 65 ABir stringi tersine çevirmek için baytları değil rune'ları ters çevirmek gerekir; aksi halde çok baytlı harflerin baytları yer değiştirir ve geçersiz UTF-8 ortaya çıkar. string(r[1]) tek bir rune'u stringe çevirir. Burada küçük bir uyarı: spesifikasyon, bir tamsayıyı string(65) biçiminde stringe çevirmenin tarihsel nedenlerle dilde kaldığını, ileride kaldırılabileceğini ve go vet aracının bu tür dönüşümleri işaretlediğini belirtir. Sayıyı ondalık metne çevirmek istiyorsanız strconv.Itoa kullanın; ayrıntı Go strconv yazısında. Bir kod noktasını kodlamak için utf8.AppendRune veya utf8.EncodeRune önerilir.
Kombinasyon karakterleri ayrı bir dikkat noktasıdır. Go Blog, "à" gibi bir karakterin tek bir kod noktasıyla (U+00E0) ya da "a" ile birleşen aksan kod noktasının (U+0300) ardışık dizisiyle yazılabileceğini ve Go'nun stringlerdeki karakterlerin normalleştirildiğini garanti etmediğini hatırlatır. Yani rune sayısı her zaman kullanıcının gördüğü harf sayısına eşit olmayabilir.
Stringler değiştirilemez
Bir stringin tek bir baytını değiştirmeye çalışmak derleme hatası verir, çünkü spesifikasyona göre s[i] atanamaz:
package main
func main() {
s := "kod"
s[0] = 'K'
}# command-line-arguments
./main.go:5:2: cannot assign to s[0] (neither addressable nor a map index expression)Değişiklik gerekiyorsa stringi []byte ya da []rune dilimine çevirip dilimi değiştirir, sonra yeniden stringe dönüştürürsünüz. Değiştirilemezlik bir avantaj da sağlar: Go Blog'a göre alt string almak çok ucuzdur, çünkü yalnızca küçük bir string başlığı oluşturulur ve orijinal ile alt string aynı diziyi güvenle paylaşabilir. Dilimlerdeki bellek tutma uyarısı burada da geçerlidir: büyük bir stringten alınan küçük bir alt string, büyük stringin baytlarını canlı tutabilir. Bu konunun dilim tarafı için Go slice append ve copy yazısına bakın.
Bayt indeksiyle kesmenin tehlikesi
s[:n] dilimlemesi bayt konumlarıyla çalışır. Çok baytlı bir harfin ortasından keserseniz geçersiz UTF-8 içeren bir string elde edersiniz. İlk N harfi güvenli biçimde almak için range döngüsünün verdiği bayt indekslerini kullanabilirsiniz:
package main
import (
"fmt"
"unicode/utf8"
)
func ilkN(s string, n int) string {
i := 0
for j := range s {
if i == n {
return s[:j]
}
i++
}
return s
}
func main() {
s := "Şubat"
fmt.Printf("%q %q\n", s[:1], s[:2]) // bayt indeksiyle kesme
fmt.Println(ilkN(s, 3), ilkN("ab", 5))
fmt.Println(utf8.ValidString(s[:1]))
}
// Çıktı:
// "\xc5" "Ş"
// Şub ab
// false"Şubat" stringinin ilk baytı tek başına anlamlı değildir; %q fiili onu "\xc5" kaçışıyla gösterir ve utf8.ValidString false döndürür. ilkN fonksiyonu ise her rune'un başlangıç konumunu sayarak doğru sınırdan keser. Kullanıcı adı kısaltma, özet üretme ya da veritabanı sütun sınırına göre kırpma gibi işlerde bu ayrım önemlidir.
Geçersiz UTF-8 ve unicode/utf8 paketi

Spesifikasyona göre range döngüsü geçersiz bir UTF-8 dizisiyle karşılaşırsa ikinci değer olarak 0xFFFD (Unicode yer tutucu karakteri) üretir ve bir sonraki yinelemede yalnızca bir bayt ilerler. unicode/utf8 paketi doğrulama, çözme ve kodlama için yardımcı fonksiyonlar sunar; Go Blog, range döngüsü ile utf8.DecodeRuneInString fonksiyonunun aynı yineleme dizisini üretecek şekilde tanımlandığını belirtir.
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func main() {
bozuk := "a\xffb"
var kodlar []string
for i, r := range bozuk {
kodlar = append(kodlar, fmt.Sprintf("%d:%U", i, r))
}
fmt.Println(kodlar)
fmt.Println(utf8.ValidString(bozuk), utf8.ValidString("çğ"))
fmt.Printf("%q\n", strings.ToValidUTF8(bozuk, "?"))
r, boyut := utf8.DecodeRuneInString("ğx")
fmt.Printf("%c %d %d\n", r, boyut, utf8.RuneLen('€'))
}
// Çıktı:
// [0:U+0061 1:U+FFFD 2:U+0062]
// false true
// "a?b"
// ğ 2 3utf8.ValidString stringin tamamen geçerli UTF-8 olup olmadığını söyler. strings.ToValidUTF8 geçersiz bayt dizilerini verdiğiniz yedek metinle değiştirir. utf8.DecodeRuneInString ilk rune'u ve kaç bayt tuttuğunu döndürür; utf8.RuneLen bir rune'un UTF-8 ile kaç bayt tutacağını verir ("€" için 3). Dosyadan, ağdan ya da kullanıcıdan gelen metin geçerli UTF-8 olmayabilir; dış veriyi işlerken doğrulama yapmak iyi bir alışkanlıktır.
| İhtiyaç | Kullanılacak araç | Sonuç birimi |
|---|---|---|
| Bayt sayısı | len(s) | bayt |
| Harf (rune) sayısı | utf8.RuneCountInString(s) | rune |
| i. bayt | s[i] | byte (uint8) |
| Harf harf gezinme | for i, r := range s | bayt indeksi ve rune |
| Harflere rastgele erişim | []rune(s) | rune dilimi (kopya) |
| Değiştirilebilir baytlar | []byte(s) | bayt dilimi (kopya) |
| Geçerlilik kontrolü | utf8.ValidString(s) | bool |
| İlk rune ve genişliği | utf8.DecodeRuneInString(s) | rune ve bayt sayısı |
Türkçe büyük ve küçük harf dönüşümü
strings.ToUpper ve strings.ToLower Unicode'un genel harf eşlemesini kullanır. Türkçede ise "i" harfinin büyüğü "İ", "ı" harfinin büyüğü "I" olduğu için genel eşleme yanlış sonuç verir. unicode paketi bu tür dile özgü eşlemeler için SpecialCase tipini ve hazır unicode.TurkishCase değerini sunar; strings.ToUpperSpecial ve strings.ToLowerSpecial fonksiyonları bu eşlemeyi uygular:
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "istanbul ılık"
fmt.Println(strings.ToUpper(s))
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, s))
k := "IŞIK İZMİR"
fmt.Printf("%q\n", strings.ToLower(k))
fmt.Printf("%q\n", strings.ToLowerSpecial(unicode.TurkishCase, k))
fmt.Println(strings.EqualFold("GO", "go"), strings.EqualFold("I", "ı"))
}
// Çıktı:
// ISTANBUL ILIK
// İSTANBUL ILIK
// "işik izmir"
// "ışık izmir"
// true falseGenel eşleme "istanbul" kelimesini "ISTANBUL" yapar, Türkçe eşleme ise doğru biçimde "İSTANBUL" üretir. Küçültmede fark "I" harfinde ortaya çıkar: genel eşleme "IŞIK" kelimesini "işik" yapar, Türkçe eşleme "ışık" verir. Örnekteki strings.EqualFold("I", "ı") sonucunun false olmasına da dikkat edin: belgeye göre EqualFold basit Unicode harf katlamasıyla çalışır ve dile özgü kuralları uygulamaz. Türkçe kullanıcı girdisini büyük-küçük harf duyarsız karşılaştırmanız gerekiyorsa iki tarafı da ToLowerSpecial(unicode.TurkishCase, ...) ile küçültüp karşılaştırmak daha öngörülebilir sonuç verir.
strings.Title fonksiyonu belgede kullanımdan kaldırılmış (deprecated) olarak işaretlidir; kelime sınırı kuralı Unicode noktalamasını doğru işlemediği için belge golang.org/x/text/cases paketini önerir. Bu paket standart kütüphanenin dışındadır.
strings paketinin sık kullanılan fonksiyonları
strings paketi arama, bölme, birleştirme, kırpma ve değiştirme için UTF-8 farkındalıklı fonksiyonlar sunar. Aşağıdaki örnek bir yapılandırma satırını ayrıştırarak birçoğunu birlikte gösteriyor:
package main
import (
"fmt"
"strings"
)
func main() {
s := " ad=Ayşe; şehir=Muğla "
s = strings.TrimSpace(s)
for _, parca := range strings.Split(s, "; ") {
anahtar, deger, bulundu := strings.Cut(parca, "=")
fmt.Println(anahtar, deger, bulundu)
}
fmt.Println(strings.Contains(s, "Muğla"), strings.Index(s, "şehir"))
fmt.Println(strings.HasPrefix(s, "ad="), strings.Count(s, "a"))
fmt.Println(strings.ReplaceAll(s, "=", ": "))
fmt.Println(strings.Fields(" bir iki\tüç "), len(strings.Fields(" bir iki\tüç ")))
fmt.Println(strings.Join([]string{"a", "b", "c"}, "-"), strings.Repeat("ab", 3))
var b strings.Builder
for i := 3; i > 0; i-- {
fmt.Fprintf(&b, "%d...", i)
}
b.WriteString("başla")
fmt.Println(b.String(), b.Len())
}
// Çıktı:
// ad Ayşe true
// şehir Muğla true
// true 10
// true 2
// ad: Ayşe; şehir: Muğla
// [bir iki üç] 3
// a-b-c ababab
// 3...2...1...başla 18strings.Cut stringi ilk ayraçtan ikiye böler ve ayracın bulunup bulunmadığını üçüncü değer olarak döndürür; Index ile elle dilimleme yapmaktan daha okunaklıdır. Go 1.27 sürüm notları, son ayraç üzerinden bölen yeni strings.CutLast fonksiyonunu duyurur ve bunun bazı LastIndex kullanımlarını sadeleştirebileceğini belirtir. strings.Index sonucu bayt konumudur (örnekte 10); rune konumu değildir. Fields bir veya daha fazla boşluk karakterine göre böler ve boş parça üretmez, Split ise ayracı tam eşleşmeyle kullanır.
Döngü içinde + ile string birleştirmek her adımda yeni bir string oluşturur. strings.Builder tipi, belgesine göre kopyalamayı en aza indirerek string oluşturur; sıfır değeri kullanıma hazırdır ve sıfır olmayan bir Builder kopyalanmamalıdır. fmt.Fprintf(&b, ...) ile doğrudan Builder'a yazabilirsiniz; örnekteki b.Len() sonucu 18 bayttır, çünkü "ş" iki bayt tutar. Biçimlendirme fiillerinin tamamı için Go fmt ve Printf, düzenli ifadeler için Go regexp yazısına bakın.
Go 1.27 sürüm notlarına göre unicode paketi ve sistemdeki ilgili destek Unicode 15'ten Unicode 17'ye yükseltildi. Harf sınıflandırması (unicode.IsLetter gibi) ve harf eşlemeleri bu sürüme göre çalışır.
Sık yapılan hatalar
![Sık yapılan hatalar: len ile harf saymak, s[i] ile harf almak, Bayt konumundan kesmek, Türkçe metinde ToUpper/ToLower](/assets/blog/go-string-rune-byte-utf8-b8.webp)
- len ile harf saymak:
len("ğüş")6 döndürür. Harf sayısı içinutf8.RuneCountInStringkullanın. - s[i] ile harf almak: indeksleme bayt verir. Harf için
rangeya da[]runekullanın. - Bayt konumundan kesmek:
s[:n]çok baytlı harfin ortasından keserek geçersiz UTF-8 üretebilir. - Türkçe metinde ToUpper/ToLower: "i" ve "ı" harfleri yanlış dönüşür;
unicode.TurkishCaseile Special fonksiyonlarını kullanın. - string(sayi) ile sayıyı metne çevirmek:
string(65)"65" değil "A" üretir;strconv.Itoakullanın. - Stringi yerinde değiştirmeye çalışmak:
s[0] = 'K'derlenmez; bayt veya rune dilimine çevirin. - Döngüde + ile birleştirmek: çok parçalı metin üretirken
strings.Builderveyastrings.Jointercih edin.
Derleyici hata mesajlarının toplu listesi için Go sık karşılaşılan hatalar yazısına bakabilirsiniz.
Sık Sorulan Sorular
Go stringleri her zaman UTF-8 midir?
Hayır. Go Blog'un vurguladığı gibi yalnızca string literalleri (bayt kaçışı içermedikçe) UTF-8'dir, çünkü Go kaynak kodu UTF-8 olarak tanımlanmıştır. Çalışma anında oluşan bir string herhangi baytları tutabilir. Geçerliliği utf8.ValidString ile kontrol edebilirsiniz.
rune ile byte arasındaki fark nedir?
byte, uint8 takma adıdır ve tek bir baytı temsil eder. rune, int32 takma adıdır ve bir Unicode kod noktasını temsil eder. ASCII karakterler tek bayt olduğundan ikisi örtüşür; Türkçe harflerde bir rune iki bayt tutar.
Bir stringdeki harfleri nasıl tersine çeviririm?
Stringi []rune dilimine çevirin, dilimi ters çevirin ve string(r) ile geri dönüştürün. Baytları ters çevirmek çok baytlı harfleri bozar. Birleşik aksan karakterleri içeren metinlerde rune bazlı ters çevirme de beklenmedik sonuç verebilir, çünkü Go karakter normalleştirmesi yapmaz.
Türkçe harfleri büyük-küçük harf duyarsız nasıl karşılaştırırım?
strings.EqualFold dile özgü kuralları uygulamaz ve "I" ile "ı" harflerini eşit saymaz. İki stringi strings.ToLowerSpecial(unicode.TurkishCase, s) ile küçültüp == ile karşılaştırabilirsiniz.
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 temel veri tipleri · Go array ve slice farkı · Go strconv · Go fmt ve Printf · Go regexp · Regex başlangıç rehberi