Go generics'te kısıt (constraint), bir tip parametresine hangi tiplerin verilebileceğini tanımlayan bir arayüzdür. Kısıt yalnızca metot isteyebilir (fmt.Stringer), belirli tipleri listeleyebilir (int | float64), ~int yazımıyla alttaki tipi int olan bütün tipleri kapsayabilir ya da bunları birleştirebilir. Önceden tanımlı comparable kısıtı == ile karşılaştırılabilen tipleri, standart kütüphanedeki cmp.Ordered ise < ile sıralanabilen tipleri ifade eder. Kısıt aynı zamanda generic fonksiyonun gövdesinde hangi işlemlerin yapılabileceğini belirler.
Bu yazıda arayüzlerin tip kümesi olarak yorumlanmasını, birleşim (union) ve ~ terimlerini, comparable'ın inceliklerini, cmp paketini (Ordered, Compare, Less, Or), yerleşik min ve max fonksiyonlarını ve [S ~[]E, E any] desenini Go 1.27.3 ile çalıştırılmış örneklerle anlatıyoruz. Generic fonksiyon ve tip yazmanın temeli Go generics yazısında; serinin tamamı Go öğrenme rehberi sayfasında.
Arayüz bir tip kümesidir

Go 1.18'den önce bir arayüz, bir metot kümesi olarak düşünülürdü. Go Blog'daki giriş yazısı aynı şeye ikinci bir açıdan bakmayı önerir: arayüz, o metotları uygulayan tiplerin kümesini tanımlar. Bu bakış, kümeye doğrudan tip eklemeyi mümkün kılar. Spesifikasyon kuralları şöyle sıralar:
- Boş arayüzün tip kümesi, arayüz olmayan bütün tiplerdir.
- Boş olmayan bir arayüzün tip kümesi, elemanlarının tip kümelerinin kesişimidir.
- Bir metot belirtiminin tip kümesi, metot kümesinde o metot bulunan bütün tiplerdir.
- Arayüz olmayan tek bir tip terimi (
int) yalnızca o tipi içerir. ~Tterimi, alttaki tipiTolan bütün tipleri içerir.t1 | t2 | ... | tnbirleşimi, terimlerin tip kümelerinin birleşimidir.
Kesişim kuralı ilginç bir sonuç doğurur: interface{ int; string } yazarsanız hem int hem string olan bir tip olmadığı için boş bir tip kümesi elde edersiniz. Kısıt kısayolu da var: tip parametresi listesinde kısıt yalnızca tek bir tip elemanından oluşuyorsa çevresindeki interface{ } yazılmayabilir; [T ~int], [T interface{~int}] ile aynıdır.
Birleşim ve ~ farkı
Aşağıdaki Sayi kısıtı üç terimin birleşimi ve her terim ~ ile yazılmış. Bu sayede int64 tabanlı kendi Kurus tipimiz de kabul ediliyor ve sonuç tipi Kurus olarak korunuyor:
package main
import "fmt"
type Sayi interface {
~int | ~int64 | ~float64
}
func Topla[T Sayi](s []T) T {
var t T
for _, v := range s {
t += v
}
return t
}
type Kurus int64 // alttaki tip int64
func main() {
fmt.Println(Topla([]int{1, 2, 3}))
fmt.Println(Topla([]float64{0.5, 0.25}))
fiyatlar := []Kurus{1250, 990}
fmt.Println(Topla(fiyatlar))
fmt.Printf("%T\n", Topla(fiyatlar))
}
// Çıktı:
// 6
// 0.75
// 2240
// main.KurusAynı kısıtı ~ olmadan int | int64 | float64 diye yazarsanız tip kümesi yalnızca bu üç önceden tanımlı tipi içerir ve Kurus dışarıda kalır. Go 1.27.3'ün hata mesajı eksik olanı doğrudan önerir:
# command-line-arguments
./main.go:20:19: Kurus does not satisfy Sayi (possibly missing ~ for int64 in Sayi)Giriş yazısının dediği gibi kısıtlarda genellikle tek bir tiple değil, o tipten türetilmiş bütün tiplerle ilgileniriz; ~ bunun için vardır. Spesifikasyonun koyduğu sınırlar da var: ~T teriminde T'nin alttaki tipi kendisi olmalıdır (~Kurus geçersizdir, ~int64 geçerlidir) ve T bir arayüz olamaz. Birleşimdeki arayüz olmayan terimlerin tip kümeleri birbirinden ayrık olmalıdır; ~int | Benim (Benim, int tabanlı) yazarsanız derleyici overlapping terms main.Benim and ~int hatası verir.
Kısıt arayüzleri değişken tipi olamaz
Yalnızca metotlardan oluşan arayüzlere spesifikasyon temel arayüz (basic interface) der. Tip terimleri, birleşimler veya comparable içeren arayüzler temel değildir ve yalnızca kısıt olarak kullanılabilir; bir değişkenin, alanın veya fonksiyon parametresinin tipi olamazlar:
package main
type Sayi interface{ ~int | ~float64 }
var x Sayi
func main() {}# command-line-arguments
./main.go:5:7: cannot use type Sayi outside a type constraint: interface contains type constraintsBu kural, "int ya da string tutabilen bir değişken" gibi varyant tipler aradığınızda generics'in doğru araç olmadığını da gösterir. Böyle bir değer için hâlâ any ve type switch ya da ortak bir metot arayüzü kullanılır. Spesifikasyon bir uygulama kısıtlaması da belirtir: birden fazla terimli bir birleşim, comparable'ı veya metot belirten arayüzleri içeremez.
Tip ve metot birlikte: karma kısıtlar

Bir kısıt hem tip terimleri hem metotlar içerebilir; tip kümesi ikisinin kesişimidir. Aşağıdaki EtiketliInt, alttaki tipi int olan ve String metodu bulunan tipleri kabul ediyor. Böylece gövdede hem v++ hem v.String() yazılabiliyor:
package main
import "fmt"
// Hem alttaki tipi int olmalı hem de String metodu bulunmalı.
type EtiketliInt interface {
~int
String() string
}
type Seviye int
func (s Seviye) String() string {
return [...]string{"DÜŞÜK", "ORTA", "YÜKSEK"}[s]
}
func Yukselt[T EtiketliInt](v T) (T, string) {
v++
return v, v.String()
}
func main() {
fmt.Println(Yukselt(Seviye(0)))
}
// Çıktı:
// ORTA ORTAİlk "ORTA", fmt.Println'in Seviye değerini String metoduyla yazdırmasından; ikincisi ise fonksiyonun döndürdüğü metinden geliyor. Go Blog'daki "Generic interfaces" yazısı bir adım daha ileri gider ve arayüzlerin kendilerinin de tip parametresi alabileceğini gösterir: type Comparer[T any] interface{ Compare(T) int } tanımlanıp [E Comparer[E]] kısıtıyla "kendisiyle karşılaştırılabilen tip" ifade edilir. time.Time, Compare(Time) int metodu sayesinde Comparer[time.Time]'ı uygular.
comparable: eşitlik kısıtı
comparable, önceden tanımlı bir kısıttır ve spesifikasyona göre katı biçimde karşılaştırılabilen (strictly comparable) arayüz olmayan bütün tipleri ifade eder. Bool, sayı, string, işaretçi ve kanal tipleri katı karşılaştırılabilirdir; alanlarının hepsi böyle olan struct'lar ve elemanları böyle olan diziler de öyledir. Dilim, map ve fonksiyon tipleri karşılaştırılamaz:
package main
import "fmt"
func Indeks[T comparable](s []T, aranan T) int {
for i, v := range s {
if v == aranan {
return i
}
}
return -1
}
type Nokta struct{ X, Y int }
func main() {
fmt.Println(Indeks([]string{"a", "b", "c"}, "c"))
fmt.Println(Indeks([]Nokta{{1, 2}, {3, 4}}, Nokta{3, 4}))
// any, comparable kısıtını sağlar (Go 1.20); karşılaştırma çalışma zamanında panik verebilir.
karisik := []any{1, "iki", []int{3}}
fmt.Println(Indeks(karisik, any("iki")))
fmt.Println(Indeks(karisik, any([]int{3})))
}
// Çıktı:
// 2
// 1
// 1
// panic: runtime error: comparing uncomparable type []intSon iki satır, Go 1.20'de eklenen bir istisnayı gösteriyor. Arayüz tipleri == ile karşılaştırılabilir ama katı karşılaştırılabilir değildir, çünkü içlerindeki dinamik değer bir dilim olabilir. Spesifikasyona göre bu yüzden any, comparable'ı uygulamaz ama onu sağlar (satisfies): Go 1.20'den beri karşılaştırılabilir (katı olması gerekmeyen) bir tip argümanı da comparable kısıtını sağlayabilir. Bunun bedeli, spesifikasyonun açıkça belirttiği gibi, tip parametresi tipindeki işlenenlerin karşılaştırılmasının çalışma zamanında paniğe yol açabilmesidir. Dilim gibi hiç karşılaştırılamayan bir tipi ise derleyici doğrudan reddeder: Indeks([][]int{{1}}, []int{1}) çağrısı []int does not satisfy comparable hatası verir.
cmp paketi: Ordered, Compare, Less ve Or
Go 1.21 sürüm notlarına göre cmp paketi, Ordered kısıtını ve sıralı değerleri karşılaştıran Less ve Compare fonksiyonlarını tanımlamak için eklendi. cmp.Ordered, <, <=, >=, > işleçlerini destekleyen bütün tipleri kapsar: ~ ile yazılmış bütün tam sayı tipleri, ~uintptr, ~float32, ~float64 ve ~string. Paket belgesi, Go'ya gelecekte yeni sıralı tipler eklenirse kısıtın da güncelleneceğini belirtir. Aynı sürümde dile yerleşik min ve max fonksiyonları da eklendi.
package main
import (
"cmp"
"fmt"
"math"
)
func EnBuyuk[T cmp.Ordered](s ...T) T {
m := s[0]
for _, v := range s[1:] {
if v > m {
m = v
}
}
return m
}
func main() {
fmt.Println(EnBuyuk(3, 9, 4), EnBuyuk("elma", "armut", "zeytin"))
fmt.Println(max(3, 9, 4), min(2.5, 1.5))
fmt.Println(cmp.Compare(2, 5), cmp.Compare("b", "a"), cmp.Compare(1.0, 1.0))
fmt.Println(cmp.Compare(math.NaN(), 1.0), cmp.Less(math.NaN(), math.Inf(-1)))
fmt.Println(math.NaN() < 1.0, math.NaN() == math.NaN())
ad := ""
fmt.Println(cmp.Or(ad, "misafir"), cmp.Or(0, 0, 8))
}
// Çıktı:
// 9 zeytin
// 9 1.5
// -1 1 0
// -1 true
// false false
// misafir 8cmp.Compare, x küçükse -1, eşitse 0, büyükse +1 döndürür. Kayan noktalı sayılarda belgenin tanımladığı tutarlı bir davranış vardır: NaN, NaN olmayan her değerden küçük sayılır, NaN NaN'a eşit sayılır ve -0.0, 0.0'a eşittir. Normal işleçler ise NaN içeren her karşılaştırmada false verir; dördüncü ve beşinci satırlar bu farkı gösteriyor. Sıralama fonksiyonlarına karşılaştırıcı yazarken bu yüzden cmp.Compare tercih edilir. cmp.Or ise comparable kısıtlıdır ve sıfır değere eşit olmayan ilk argümanı döndürür; varsayılan değer atamak ya da paket belgesindeki örnekte olduğu gibi çok anahtarlı sıralamada karşılaştırmaları zincirlemek için kullanılır. slices.SortFunc ile birlikte kullanımı slices ve sort yazısında anlatılıyor.
[S ~[]E, E any] deseni
Giriş yazısındaki Scale örneği, kısıttan tip çıkarımının neden önemli olduğunu gösterir. Bir dilimin elemanlarını çarpan fonksiyonu func Olcekle[E Sayi](s []E, c E) []E diye yazarsanız, []int32 tabanlı ve kendi metotları olan bir Nokta tipi verdiğinizde sonuç Nokta değil düz []int32 olur ve String metodu kaybolur. Çözüm, dilim tipinin kendisini de bir tip parametresi yapmaktır:
package main
import (
"fmt"
"strconv"
)
// S ~[]E: S, eleman tipi E olan herhangi bir dilim tipi olabilir.
func Olcekle[S ~[]E, E ~int | ~int32 | ~float64](s S, c E) S {
r := make(S, len(s))
for i, v := range s {
r[i] = v * c
}
return r
}
type Nokta []int32
func (n Nokta) String() string {
s := "Nokta("
for i, v := range n {
if i > 0 {
s += ","
}
s += strconv.Itoa(int(v))
}
return s + ")"
}
func main() {
p := Nokta{1, 2, 3}
r := Olcekle(p, 2) // S=Nokta, E=int32 çıkarıldı
fmt.Println(r.String())
fmt.Println(Olcekle([]float64{1.5}, 2))
}
// Çıktı:
// Nokta(2,4,6)
// [3]Olcekle(p, 2) çağrısında S, argümandan Nokta olarak çıkarılıyor. 2 tipsiz bir sabit olduğu için E ondan çıkarılamaz; giriş yazısının anlattığı gibi derleyici E'yi S'nin kısıtından (~[]E) bulur: Nokta'nın alttaki tipi []int32 olduğuna göre E int32'dir. Standart kütüphanedeki slices fonksiyonlarının çoğu, örneğin slices.Sort[S ~[]E, E cmp.Ordered](x S), bu yüzden aynı imza desenini kullanır.
Metot isteyen kısıt mı, karşılaştırma fonksiyonu mu?
Sıralı bir veri yapısı yazarken iki seçeneğiniz vardır: eleman tipinden Compare gibi bir metot isteyen bir kısıt tanımlamak ya da yapıya bir karşılaştırma fonksiyonu vermek. "When To Use Generics" yazısı genel amaçlı veri tipleri için fonksiyonu önerir. Gerekçesi şu: metot isteyen bir kısıt kullanırsanız ağaç yapısını int gibi basit bir tiple kullanmak isteyen herkes kendi tam sayı tipini tanımlayıp ona metot yazmak zorunda kalır. Fonksiyon parametresine ise cmp.Compare[int] gibi hazır bir fonksiyon verilebilir; eleman tipinin zaten bir Compare metodu varsa Tip.Compare metot ifadesi doğrudan fonksiyon olarak kullanılır. Yazının deyişiyle bir metodu fonksiyona çevirmek, bir tipe metot eklemekten çok daha kolaydır.
"Generic interfaces" yazısı bu seçimin bedellerini de sıralar: fonksiyon alanı olan bir yapının sıfır değeri doğrudan kullanılamaz, çünkü karşılaştırma fonksiyonunun açıkça verilmesi gerekir; ayrıca fonksiyon alanı üzerinden yapılan çağrıları derleyicinin satır içine alması (inline) zorlaşabilir. [E Comparer[E]] gibi metot isteyen bir kısıt ise sıfır değeri kullanılabilir tutar. Yazı, tam esneklik isteyen bir kütüphanenin üç sürümü birden sunabileceğini ve ortak uygulama için en genel olan fonksiyonlu sürümün kullanılabileceğini belirtir.
Kısıt biçimleri tablosu

| Kısıt | Kabul ettiği tipler | Gövdede izin verdiği |
|---|---|---|
| any | Her tip | Atama, adres alma, any olarak geçirme |
| comparable | Karşılaştırılabilen tipler (Go 1.20 istisnasıyla arayüzler dahil) | == ve != |
| cmp.Ordered | Tam sayılar, kayan noktalılar, stringler ve türevleri | < <= > >= == != |
| int | float64 | Yalnızca bu iki tip | İkisinin de desteklediği işlemler |
| ~int | ~float64 | Alttaki tipi int veya float64 olan bütün tipler | İkisinin de desteklediği işlemler |
| interface{ String() string } | String metodu olan tipler | String çağırmak |
| interface{ ~int; String() string } | int tabanlı ve String metodu olan tipler | Sayı işlemleri ve String |
Sık yapılan hatalar
- ~ işaretini unutmak:
int64yazan bir kısıt,type Kurus int64gibi tanımlı tipleri dışarıda bırakır. Derleyici mesajı "possibly missing ~" diye ipucu verir. - comparable ile sıralama yapmaya çalışmak:
<içincmp.Orderedgerekir. - comparable'ı tam güvence sanmak:
anygibi arayüz tip argümanlarıyla eşitlik karşılaştırması çalışma zamanında panik verebilir. - Kısıt arayüzünü değişken tipi olarak kullanmak: Tip terimi içeren arayüzler yalnızca kısıttır.
- Çakışan birleşim terimleri:
~int | Benimgibi kesişen terimler derleme hatasıdır. - []E yerine S ~[]E yazmamak: Fonksiyon tanımlı dilim tiplerinin adını ve metotlarını kaybettirir.
- NaN'ı unutmak: Kayan noktalı sayıları
<ile sıralarken NaN tutarsız sonuç verir;cmp.Comparekullanın.
Sık Sorulan Sorular
Go'da neden Rust'taki gibi standart Add veya Eq trait'leri yok?
FAQ'a göre Rust'ta bir trait ile tip arasındaki ilişki açıkça tanımlanmalıdır; Go'da ise tip argümanları kısıtları, tiplerin arayüzleri uyguladığı gibi örtük olarak sağlar. Toplama gibi işlemler kullanıcı kodunda tip kümesi arayüzleriyle ifade edilebildiği için standart kütüphane bunları tanımlamaz. Tek istisna, tip sisteminde başka türlü ifade edilemeyen bir özelliği yakalayan comparable'dır.
cmp.Ordered ile constraints.Ordered arasında fark var mı?
cmp.Ordered standart kütüphanededir ve Go 1.21'den beri kullanılabilir. constraints paketi golang.org/x/exp altında harici bir modüldür. Yeni kodda harici bağımlılık gerektirmeyen cmp.Ordered tercih edilir.
Bir kısıtta metot ve tip birlikte olabilir mi?
Evet. Tip kümesi, tip terimleri ile metot belirtimlerinin kesişimidir. Yukarıdaki EtiketliInt örneği ~int ile String() string'i birleştiriyor. Ancak birden fazla terimli bir birleşim metot belirten arayüzleri içeremez.
min ve max için kısıt yazmam gerekir mi?
Hayır. Go 1.21'den beri min ve max dilin yerleşik fonksiyonlarıdır ve sıralı tiplerde sabit sayıda argümanla doğrudan çalışır. Dilimdeki en küçük veya en büyük elemanı bulmak için slices.Min ve slices.Max kullanılabilir.
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 generics · Go interface · slices, maps ve sort · Go temel veri tipleri · Go type switch