Ders 02 / 13
Boş Arayüz ve `any`
Boş arayüz sıfır yöntem istiyor, ve sıfır yöntemi her tip karşılıyor: kaynakta hangi tipin geleceği hiç yazılı değil, tip bilgisi değerin içinde taşınıyor. Aynı değer üç ayrı statik tiple taşındığında dinamik tip değişmiyor; bedeli, değere konan her şeyin çift hâline gelip doğrudan çağrılamaması. Sınırlayıcı ölçüm: nil bir işaretçi boş arayüze konduğunda arayüzün kendisi nil değil.
İçindekiler
Önceki ders arayüz değerinin dinamik tipini bir kez okudu — %T ile main.Dosya — ve
üzerinde durmadı. Boş arayüz tam olarak bu satırın kendisi: hiçbir yöntem istemeyen bir
arayüz, kayıt sayısıyla değil sıfır sayısıyla tanımlanan bir arayüz. Kaynakta hangi tipin bu
arayüze konacağı hiç yazılı olmadığı için, konan tipin bilgisini birinin bir yerde taşıması
gerekiyor; önceki dersin bağlayıcı dağılımında o yerin adı zaten geçmişti — değer. Bu
ders o satırı büyütüyor.
Önceki dersteki Yazar ve Kapatan gibi arayüzler birer yöntem istiyordu; Gomulu ikisini
birden istiyordu. Boş arayüz bu sıralamanın öbür ucunda duruyor: hiç yöntem istemiyor. Bir
arayüzün ne kadar az istediği ile onu ne kadar çok tipin karşılayabildiği arasındaki ilişki
önceki derste sayıyla ölçülmüştü; bu ders o eğrinin sıfır noktasına odaklanıyor ve orada
neyin kazanılıp neyin kaybedildiğini ölçüyor.
Sıfır Yöntem İsteyen Arayüz
Önceki derste yöntem sayısı bir, iki, üçe çıktıkça, aynı dört tipten onu karşılayabilenlerin
sayısı üç, iki, bire düşüyordu. Boş arayüz bu eğrinin sıfır ucu: interface{} hiç yöntem
istemiyor, ve hiçbir yöntemi olmayan bir koşulu her tip karşılıyor — boş bir küme her kümenin
alt kümesidir. Dilin kendi tip adı any, interface{}in kısaltması; bu ders arayüzün
kavramına boş arayüz, kod içindeki adına any diyor.
Boş arayüz hiçbir yöntem istemediği için hangi tipin ona konacağını kaynak hiç
sınırlamıyor: var v any = 42, var v any = "metin", var v any = Birim{} üçü de
derleniyor, çünkü üçünün de karşılaması gereken sıfır yöntem var. Ama arayüzün sıfır yöntem
istemesi, konan değerin hiçbir bilgisinin kalmadığı anlamına gelmiyor. Tip bilgisi
kaybolmuyor, yalnız görünürlüğü değişiyor — kaynaktan okunabilen bir tip adı olmaktan çıkıp
çalışma zamanında sorulması gereken bir bilgiye dönüşüyor.
Bu ders boyunca yazılan her fmt.Println çağrısı da aslında aynı düzeneği kullanıyor:
standart kütüphanenin bu işlevi, parametrelerini ...any olarak alıyor. Bir tam sayı, bir
dizgi ya da bir yapı — hangisi verilirse verilsin, çağrı sırasında her biri boş arayüze
konuyor ve Printlnin içinde %T benzeri bir sorgulamayla geri okunuyor. Boş arayüz bu
yüzden nadir görülen bir kenar durum değil; bu dersteki her çıktı satırı zaten ondan geçti.
Aynı Değer, Üç Statik Tip
Bir değerin dinamik tipi, o değerin hangi statik tip üzerinden taşındığından bağımsız.
Aşağıdaki ölçüm aynı Birim değerini üç ayrı statik tipin arkasından geçiriyor — kendi
arayüzü, sonra boş arayüz, sonra doğrudan boş arayüz — ve her seferinde %T ile dinamik tipi
okuyor.
// ayni-deger.go — ayni deger uc ayri statik tipte tasindiginda dinamik tip degismiyor
package main
import "fmt"
type Birim struct{ Ad string }
func (b Birim) Kimlik() string { return b.Ad }
type Tanimlayici interface{ Kimlik() string }
func main() {
b := Birim{Ad: "birim-42"}
var t1 Tanimlayici = b
var t2 any = t1
var t3 any = b
fmt.Println("Tanimlayici olarak :", fmt.Sprintf("%T", t1))
fmt.Println("any'ye tasindiktan sonra :", fmt.Sprintf("%T", t2))
fmt.Println("dogrudan any olarak :", fmt.Sprintf("%T", t3))
}
Tanimlayici olarak : main.Birim any'ye tasindiktan sonra : main.Birim dogrudan any olarak : main.Birim
Üç değişken üç ayrı statik tip taşıyor — Tanimlayici, sonra any, sonra yine any ama
başka bir yoldan gelmiş — ve üçü de aynı yanıtı veriyor: main.Birim. t1den t2ye geçiş
bir dönüşüm değil, aynı çiftin (tip, değer) yeniden bir değişkene bağlanması; tip bilgisi
kaybolmuyor çünkü hiçbir noktada kaybedilecek bir şey olmuyor, arayüz değeri zaten en
baştan beri bu çifti taşıyordu. Tanimlayicinin tek yöntemi de, boş arayüzün sıfır
yöntemi de, aynı temel yapıyı görüntülüyor; yalnız görünen yöntem sayısı farklı.
AR4. t3 := b yerine t3 any = b yazılmasının nedeni, Birimin doğrudan boş arayüze
konduğu bir yolu da göstermek — t1 ve t2 zincirinin Tanimlayici üzerinden geçtiğini,
t3ün geçmediğini ayırt etmek için üçüncü bir satır bilinçli olarak eklendi.
Aynı Kap, Dört Ayrı Dinamik Tip
Önceki ölçüm tek bir değeri üç ayrı statik tipten geçirdi ve dinamik tipin sabit kaldığını
gösterdi. Bu ölçüm tersini yapıyor: tek bir statik tip ([]any) içine dört ayrı dinamik tip
koyuyor ve her birinin kendi kimliğini koruduğunu gösteriyor.
// karisik.go — ayni any dilimi ic ice farkli dinamik tipler tasiyor
package main
import "fmt"
type Birim struct{ Ad string }
func main() {
degerler := []any{42, "metin", Birim{Ad: "x"}, 3.14}
for _, d := range degerler {
fmt.Printf("deger=%v dinamik tip=%T\n", d, d)
}
fmt.Println("dilimin statik tipi: []any, uzunluk:", len(degerler))
}
deger=42 dinamik tip=int
deger=metin dinamik tip=string
deger={x} dinamik tip=main.Birim
deger=3.14 dinamik tip=float64
dilimin statik tipi: []any, uzunluk: 4
AR5. Dizi bilinçli olarak dört ayrı temel kategoriden seçildi — bir tam sayı, bir dizgi, bir yapı, bir kayan noktalı sayı — ki gösterim tek bir tip ailesine (örneğin yalnız sayısal tipler) özgü bir davranış değil, boş arayüzün genel kuralı olduğu görülebilsin.
Dilimin kendisi tek bir statik tip taşıyor: []any. Ama dört ögesi dört ayrı dinamik tip
taşıyor — int, string, main.Birim, float64 — ve range döngüsü bunların hiçbirini
birbirine karıştırmıyor, her öge kendi çiftini kendisiyle birlikte getiriyor. Bu, önceki
ölçümün aynasıdır: orada aynı tip üç statik biçimde sabit kaldı, burada aynı statik biçim
dört ayrı tipi taşıyor. İkisi birlikte tip bilgisinin nerede durduğunu netleştiriyor — statik
tipte değil, her değerin kendi taşıdığı çiftte.
Bu tür heterojen bir kap — tek bir dilimde birbirinden farklı tipte değerler tutmak — tip parametreleriyle yazılan genel veri yapılarından önce yalnız boş arayüzle kurulabiliyordu. İki yazımın nerede ayrıştığı kursun genellikler bölümünde ölçülecek; burada yalnız boş arayüzün bunu nasıl yaptığı önemli: her ögeyi kendi çiftiyle birlikte saklayarak.
Bedel: Çift Olmak, Doğrudan Çağrılamamak
Tip bilgisinin değerde taşınmasının bir bedeli var: boş arayüze konan bir değer, kendi
yöntemleriyle doğrudan çağrılamıyor. any sıfır yöntem istediği için sıfır yöntem
sunuyor — üzerinde tanımlı hiçbir yöntem yok, konan değerin kendi yöntemleri olsa bile.
Çağırmak için önce tip iddiasıyla asıl tipe (ya da asıl tipin karşıladığı bir arayüze)
dönmek gerekiyor.
Bu, önceki dersteki küçük arayüz ölçümünün simetrik karşılığı. Orada yöntem sayısı arttıkça
onu karşılayan tip sayısı azalıyordu; burada yöntem sayısı sıfıra indiğinde erişim de
sıfırlanıyor. Boş arayüz her tipi kabul ediyor çünkü hiçbir şey istemiyor, ve hiçbir şey
istemediği için hiçbir şey de sunmuyor — kabul genişliği ile doğrudan erişim ters orantılı.
Derleyici burada bir tahmin yürütmüyor: vnin statik tipi any olduğu sürece, v üzerinde
hangi yöntemlerin bulunduğunu bilmesinin hiçbir yolu yok, çünkü bu bilgi ancak çalışma
zamanında değerin kendisine bakılarak öğrenilebilir.
// arac.go — bir kaynak parcasinin derlenip derlenmedigini programin icinden sinar
package main
import (
"os"
"os/exec"
"path/filepath"
)
func derlenirMi(kaynak string) bool {
dizin, err := os.MkdirTemp("", "sinama")
if err != nil {
return false
}
defer os.RemoveAll(dizin)
os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
os.WriteFile(filepath.Join(dizin, "kod.go"), []byte(kaynak), 0o644)
cmd := exec.Command("go", "build", "./...")
cmd.Dir = dizin
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
return cmd.Run() == nil
}
Bu blok yalnız diske yazılıyor; iki kaynak parçasını üretip derleyiciyi çağıran asıl ölçüm aşağıda.
// bedel.go — any uzerinden dogrudan cagri reddediliyor, tip iddiasindan sonraki cagri gecerli
package main
import "fmt"
func main() {
ortak := `package sinama
type Birim struct{ Ad string }
func (b Birim) Kimlik() string { return b.Ad }
type Tanimlayici interface{ Kimlik() string }
func F() string {
var v any = Birim{Ad: "x"}
`
dogrudanCagri := ortak + ` return v.Kimlik()
}
`
iddiaylaCagri := ortak + ` return v.(Tanimlayici).Kimlik()
}
`
fmt.Println("dogrudan cagri, any uzerinden :", derlenirMi(dogrudanCagri))
fmt.Println("tip iddiasiyla cagri :", derlenirMi(iddiaylaCagri))
}
dogrudan cagri, any uzerinden : false tip iddiasiyla cagri : true
v.Kimlik() derlenmiyor, çünkü vnin statik tipi any ve anynin yöntem kümesi boş;
derleyici Kimlik adlı bir yöntemi any üzerinde aramıyor bile, çünkü orada aranacak
hiçbir yöntem tanımlı değil. v.(Tanimlayici).Kimlik() derleniyor, çünkü tip iddiası önce
değeri Tanimlayiciye çeviriyor ve Tanimlayicinin (dolayısıyla Kimlikin) yöntem kümesi
üzerinden çağrı yapılabiliyor. Bedel şu: any bir değeri her tipten kabul ediyor, ama
karşılığında o değerin kendi davranışına doğrudan erişimi kapatıyor. Tip iddiasının kendisi
sıradaki dersin konusu; burada yalnız onun gerekliliği ölçülüyor.
Bu ret bir eksiklik değil, bilinçli bir sınır. Derleyici v.Kimlik() satırını görürken elinde
yalnız vnin statik tipi (any) var; hangi somut değerin orada duracağı derleme zamanında
henüz bilinmiyor, ilk çalıştığında belirleniyor. Derleyici bu boşluğu “muhtemelen Kimlik
vardır” diye tahmin ederek doldurmuyor — böyle bir tahmin, yanlış çıktığında ancak çalışma
zamanında, belki de üretim ortamında patlardı. Bunun yerine derleyici, statik tipin sunduğu
yöntem kümesinin dışına hiç çıkmıyor; boş arayüz için bu küme boş olduğundan, çağrı orada
duruyor. Tip iddiası bu boşluğu açıkça kapatan tek yol.
Sınırlayıcı Ölçüm: Nil İşaretçi Taşıyan Arayüz Nil Değildir
Boş arayüzün en tuhaf satırı, tip bilgisinin bedelsiz taşınmadığını gösteriyor. Bir işaretçi
nil olabilir; aynı işaretçi boş arayüze konduğunda, arayüzün kendisi nil olmuyor.
// nil-arayuz.go — isaretci nil olsa da onu tasiyan arayuz nil degil
package main
import "fmt"
type Sunucu struct{}
func main() {
var s *Sunucu
var v1 any = s
var v2 any
fmt.Println("isaretcinin kendisi nil mi :", s == nil)
fmt.Println("isaretciyi tasiyan v1 nil mi:", v1 == nil)
fmt.Println("hic deger konmayan v2 nil mi:", v2 == nil)
}
isaretcinin kendisi nil mi : true isaretciyi tasiyan v1 nil mi: false hic deger konmayan v2 nil mi: true
Üç satır bilinçli olarak yan yana konuldu. s gerçekten nil — hiçbir Sunucu örneğini
göstermiyor. v1 bu nil işaretçiyi taşıyor, ama v1 == nil false veriyor: arayüz
değeri bir çift, ve bu çiftin tip yuvası dolu — *main.Sunucu orada duruyor — yalnız değer
yuvası nil. Çift yalnız her iki yuvası da boşken nil sayılıyor; v2 hiç değer
almadığı için hem tip hem değer yuvası boş kalıyor ve gerçekten nil.
Çiftin tip yuvasının gerçekten ayrı bir bilgi taşıdığı, iki farklı işaretçi tipi yan yana konduğunda daha da netleşiyor.
// iki-nil.go — iki ayri isaretci tipinin nil degerleri birbirine esit sayilmiyor
package main
import "fmt"
type A struct{}
type B struct{}
func main() {
var a *A
var b *B
var v1 any = a
var v2 any = b
fmt.Println("v1 nil mi :", v1 == nil)
fmt.Println("v2 nil mi :", v2 == nil)
fmt.Println("v1 v2'ye esit mi:", v1 == v2)
fmt.Printf("v1 dinamik tip=%T, v2 dinamik tip=%T\n", v1, v2)
}
v1 nil mi : false v2 nil mi : false v1 v2'ye esit mi: false v1 dinamik tip=*main.A, v2 dinamik tip=*main.B
a ve b ikisi de nil işaretçi ve ikisi de sıfır alanlı, görünüşte “boş” birer yapıyı
gösteriyor. Ama v1 ile v2 birbirine eşit değil, çünkü arayüz karşılaştırması önce tip
yuvasını karşılaştırıyor ve *main.A ile *main.B aynı tip değil. İki değer yuvası da
nil olsa bile, tip yuvaları farklı olduğu için çift olarak eşit sayılmıyorlar. Bu, tip
yuvasının değer yuvasından bağımsız, gerçek bir bilgi taşıdığının bir başka kanıtı — yalnız
“dolu mu boş mu” değil, “hangi tip” sorusunun da karşılaştırmaya girdiğini gösteriyor.
AR6. Bu, önceki dersin birinci iddiasının doğrudan sonucu: bağların sekizi değerin
içinde taşınıyorsa, değerin boş olması çiftin boş olması demek değil. Go Temelleri kursunun
Sıfır Değerler dersi nil dilime eklenebilirken nil sözlüğe yazılamadığını ölçmüştü; bu
satır aynı ailenin üçüncü örneği — nil sözcüğü aynı kalıyor, taşıdığı anlam tipten tipe
değişiyor.
Bu satırın pratik sonucu şu: bir fonksiyon error (kendisi bir arayüz) döndürüyorsa ve
içeride somut bir işaretçi tipi kullanıyorsa, o işaretçi nil olsa bile döndürülen hata
değeri nil olmayabilir. Çağıran taraf hata == nil diye sorduğunda false yanıtını
alır, çünkü sorduğu şey işaretçinin kendisi değil, onu taşıyan çifttir. Bu tuzağın tam
ölçümü hata değerlerine ayrılan derste yapılacak; burada yalnız kökeni gösterildi.
Kökeni bu kadar erken görmenin bir nedeni var: hata, kendisi de bir arayüz, ve arayüz olan
her şey aynı çift kuralına tabi. error boş arayüz değil — Error() string yöntemini
istiyor — ama boş arayüzle paylaştığı şey tam olarak bu ölçümün konusu: ikisi de bir tip
yuvası ve bir değer yuvası taşıyan bir çift. Boş arayüzde bu çifti doğrudan gözlemlemek
kolay, çünkü hiçbir yöntem araya girmiyor; hata arayüzünde aynı çift bir yöntem imzasının
arkasına gizleniyor ama davranışı değişmiyor.
Özet
- Boş arayüz sıfır yöntem istiyor ve sıfır yöntemi her tip karşılıyor;
anybu arayüzün kod içindeki adı. - Aynı değer üç ayrı statik tiple (özel arayüz, ondan
anyye, doğrudanany) taşındığında dinamik tip değişmiyor — tip bilgisi kaybolmuyor, yalnız görünürlüğü değişiyor. - Boş arayüze konan bir değer doğrudan çağrılamıyor;
anynin yöntem kümesi boş olduğu için çağrı önce bir tip iddiası gerektiriyor. - Nil bir işaretçi boş arayüze konduğunda arayüzün kendisi nil değil — çift yalnız tip ve değer yuvası birlikte boşken nil sayılıyor.
- İki ayrı işaretçi tipinin
nildeğerleri boş arayüze konduğunda birbirine eşit sayılmıyor; karşılaştırma değer yuvasından önce tip yuvasına bakıyor.
Sonraki Adım
Bu derste tip bilgisine yalnız %T ile, salt okuma amacıyla erişildi — hiçbir satır
vnin gerçek değerini geri almadı, yalnız hangi tipte olduğunu okudu. Ama bir programın asıl
sorması gereken soru genellikle “bu değerin tipi ne” değil, “bu değer şu tipi karşılıyor mu”
ve karşılıyorsa “asıl değeri bana ver”. Boş arayüzden çıkışta zaten bir kez görüldü:
v.(Tanimlayici) değeri geri kazandırdı ama yanıtı denetlemeden. Sıradaki ders bu iki
soruyu ayrı ayrı ölçüyor: tip iddiasının iki yanıtlı biçimi ve tip anahtarının seçtiği dal,
değere sormanın iki ayrı yolu.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.