Ders 02 / 18
Ortam Kurulumu ve İlk Program
Bir Go kaynağının derlenebilmesi için üç şey zorunludur: paket bildirimi, giriş noktası (func main) ve modül dosyası; üçünden biri eksik olduğunda, kaynağın geri kalanı hiç değişmese bile derleme reddediliyor — modül dosyası kaynağın kendisinde hiç durmuyor.
İçindekiler
Önceki ders dört ret nedenini ölçtü; hepsi kaynağa fazladan bir şey yazıldığında ya da bir sözün tutulmadığında ortaya çıkıyordu. Bu ders tersini soruyor: bir kaynağın hiç derlenip çalışabilmesi için yazılması zorunlu olan en küçük küme nedir? Bir Go programı üç şeyi birden ister — bir paket bildirimi, bir giriş noktası, bir modül dosyası — ve bunlardan biri eksik olduğunda kaynağın geri kalanı ne kadar doğru yazılmış olursa olsun derleme durur.
Bilgisayarlar Nasıl Çalışır kursu (M01/K01) bir programın işletim sistemi tarafından nasıl başlatıldığını, giriş noktası kavramını ve bir sürecin ilk komuta nasıl ulaştığını kavram olarak kurdu. Bu burada tekrar kurulmaz — farkı tek cümleyle yazmak yeter: orada giriş noktasının var olması gerektiği anlatıldı, burada Go’nun bu gerekliliği hangi imzayla ve derleme zamanında denetlediği ölçülüyor.
Üç Zorunlu Parça
Üçü de farklı bir yerde durur. Paket bildirimi (package main) kaynağın ilk satırıdır ve
hangi paketin parçası olduğunu söyler; çalıştırılabilir bir program için bu paket adı main
olmak zorundadır. Giriş noktası, main paketi içinde tanımlanan func main() işlevidir ve
sanal makinenin değil, doğrudan işletim sisteminin çağıracağı ilk Go koludur. Modül dosyası ise
kaynağın dışındadır — kaynakla aynı dizinde duran ayrı bir metin dosyası, go.mod, ve
derleyiciye “bu dizin bir modülün köküdür” diyen tek şey odur.
Üçünün de zorunlu olması bir tesadüf değil, üç ayrı sorunun yanıtı. Paket bildirimi “bu kaynak
neyin parçası” sorusuna yanıt verir ve tek bir kaynak dosyasının bile paketsiz duramayacağını
söyler — Go’da paketsiz bir kaynak diye bir şey yoktur, en küçük program bile bir paket adı
taşımak zorundadır. Giriş noktası “çalıştırma buradan mı başlıyor” sorusuna yanıt verir; bir
paket, birden fazla işlev barındırabilir ama işletim sistemine hangi işlevden başlanacağını
söyleyen tek bir imza vardır. Modül dosyası ise “bu paket nerede duruyor” sorusuna yanıt verir
ve paketin kendisinden bağımsız bir bilgi taşır — aynı paket adı birçok farklı dizinde tekrar
edebilir, ama bir dizinin hangi modüle ait olduğu yalnız o dizindeki (ya da üstündeki) go.mod
dosyasından okunur.
Üç sorunun üçü de “zorunlu” göründüğü için, bunları ayrı ayrı doğrulamak yerine tek bir kontrol
listesi gibi ele almak cazip gelebilir — “üçü de eksiksiz mi” diye tek bir denetim. Bu ders
tam tersini yapıyor: her birini teker teker kaldırıp sonucu ayrı ayrı okuyor, çünkü aynı
derlenmedi yanıtının arkasında üç ayrı neden durabiliyor ve bu üç nedenin hangi aşamada
ortaya çıktığını bilmek, bir kaynağın neden derlenmediğini anlamanın ilk adımı.
Bu üç parçayı tek tek kaldırıp derlenme sonucunu ölçeceğiz. Ölçüm aracı, önceki dersin
derlenirMi işlevine çok yakın; tek farkı, modül dosyasının yazılıp yazılmayacağını ayrıca bir
parametre olarak almasıdır.
// derleme.go — parcalari denetleyerek derleyen yardimci
package main
import (
"os"
"os/exec"
"path/filepath"
)
func derlenirMiParcalar(kaynak string, modYaz bool) bool {
dizin, err := os.MkdirTemp("", "sinama")
if err != nil {
return false
}
defer os.RemoveAll(dizin)
if modYaz {
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
}
func sinif(ok bool) string {
if ok {
return "derlendi"
}
return "derlenmedi"
}
DT4. Ölçüm aracı bir önceki dersten tek bir parametre uzağa açılıyor: modYaz, modül
dosyasının o koşumda yazılıp yazılmayacağını belirler. Kaynağın kendisi bu parametreden hiç
etkilenmez; bu, aşağıdaki dördüncü satırın neyi ölçtüğünü doğru okumak için gerekli.
Bu genişleme küçük görünse de bir tasarım kararı taşıyor: önceki derste yazılmamışlığın yeri her zaman kaynağın içindeydi — bir satır ya vardı ya yoktu. Bu derste yazılmamışlığın yeri ilk kez kaynağın dışına taşınıyor ve ölçüm aracının bunu ayrı bir bağımsız değişkenle temsil etmesi gerekiyor. Bir sonraki bölümde bu ayrımın neden önemli olduğu görülecek.
Dört durumu sırayla koşturuyoruz: hiçbir parça eksik değilken, paket bildirimi yokken, giriş noktası yokken, modül dosyası yokken.
// parcalar.go — uc zorunlu parcanin her biri tek tek kaldirilir
package main
import "fmt"
const tamKaynak = `package main
func main() {}
`
const paketsizKaynak = `func main() {}
`
const anasizKaynak = `package main
`
func main() {
fmt.Printf("%-30s %s\n", "eksik parca", "sonuc")
fmt.Printf("%-30s %s\n", "hicbiri eksik degil", sinif(derlenirMiParcalar(tamKaynak, true)))
fmt.Printf("%-30s %s\n", "paket bildirimi yok", sinif(derlenirMiParcalar(paketsizKaynak, true)))
fmt.Printf("%-30s %s\n", "giris noktasi (main) yok", sinif(derlenirMiParcalar(anasizKaynak, true)))
fmt.Printf("%-30s %s\n", "modul dosyasi yok", sinif(derlenirMiParcalar(tamKaynak, false)))
}
eksik parca sonuc hicbiri eksik degil derlendi paket bildirimi yok derlenmedi giris noktasi (main) yok derlenmedi modul dosyasi yok derlenmedi
İlk satır kontrol grubudur: hiçbir parça eksik değilken kaynak derlenir. Sonraki üç satırın her
biri, kontrol grubundan tek bir parçayı çıkarır ve sonuç aynı yönde değişir — üçü de derlemeyi
durdurur. Ama üçü aynı nedenden durdurmaz. Paket bildirimi yokken derleyici kaynağın hangi
pakete ait olduğunu hiç bilmiyor; bu, en erken kesilen daldır, çünkü paket adı kaynağın ilk
satırında beklenir. Giriş noktası yokken kaynak geçerli bir paket olarak derlenir ama
yürütülebilir bir dosya üretilmez — kaynakta hata yoktur, yalnız çalıştırılabilir bir uçtan
yoksundur; go build ./... bunu da derlenmedi sayıyor, çünkü main paketi için giriş noktası
zorunlu bir sözleşmedir. Dördüncü satır ise en dolaylı olanı ve dersin sınırlayıcı ölçümüne
taşıyor.
paketsizKaynak sabitine bakılırsa, eksik olan tek şey bir satırdır: package main. Bu satır
kaldırıldığında geriye kalan func main() {} başlı başına anlamsız değildir — bir işlev
tanımıdır ve sözdizimi doğrudur. Derleyicinin reddi burada sözdizimine değil, bağlama
yöneliktir: her Go dosyası bir paketin parçası olarak okunur ve paket adı olmadan bir dosyayı
hiçbir pakete yerleştiremez. Bu, önceki dersteki dört retten farklı bir tondadır: oradaki
dört kaynak sözdizimi kurallarını çiğniyordu, buradaki kaynak sözdizimi açısından eksiksiz bir
işlev gövdesidir ama kimliksizdir.
İlk iki ret, önceki dersin ölçtüğü dört rete yakın bir aileden: ikisi de kaynağın kendi
sözdiziminde eksik bırakılan bir şeydir ve derleyici bunu kaynağı okurken görür. Giriş
noktasının yokluğu farklı bir aşamada yakalanıyor. go build iki işi art arda yapar: önce
kaynağı bir pakete derler, sonra o paketten çalıştırılabilir bir dosya bağlar. Paket
derlemesi main.goyu geçerli bir Go dosyası olarak kabul eder; bağlama aşaması, main
paketinin bir func main() işlevi taşımasını arar ve bulamayınca durur. Bu ayrım önemlidir,
çünkü aynı kaynak farklı bir bağlamda —örneğin başka bir programın içe aktardığı bir kitaplık
paketi olarak— hiçbir sorun çıkarmadan derlenebilirdi; giriş noktası zorunluluğu yalnız main
paketine, yalnız çalıştırılabilir bir dosya istendiğinde uygulanıyor.
İki Dosya, Tek Paket
Üç zorunlu parça, tek bir dosyada mı durmak zorunda? Değil. Aynı dizindeki iki dosya, aynı
paket bildirimini taşıdığı sürece tek pakete ait sayılır ve birlikte derlenir — biri
func main taşımasa bile. Yukarıdaki derleme.go ile bu derste kullanılan diğer bütün
dosyalar zaten bunu gösteriyordu; burada aynı deseni en yalın hâliyle kuruyoruz.
Bunun pratik önemi, bir programın büyümesiyle ortaya çıkıyor. func main() içeren tek bir
dosyaya her şeyi yazmak zorunda olsaydık, bir programın uzunluğu tek dosyanın okunabilirliğiyle
sınırlı kalırdı. Go, bir paketi dosya sayısına göre değil, paket bildirimine göre tanımladığı
için, aynı paketin sorumluluklarını birden fazla dosyaya dağıtmak mümkün — üstelik bu bölünme
derleme sonucunu hiç değiştirmiyor: derleme.go, yardim.go ve birlikte.go üç ayrı dosya
olarak da tek bir dosyaya birleştirilmiş gibi de derlenirdi, aynı ikili dosyayı üretirdi.
Bölünme kararı, tamamen okuyan ve yazan tarafın rahatlığı için var.
// yardim.go — ikinci dosya, ayni pakete ait
package main
import "fmt"
func selamla(ad string) string {
return fmt.Sprintf("merhaba, %s", ad)
}
// birlikte.go — iki ayri dosya, tek paket, birlikte derlenir
package main
import "fmt"
func main() {
fmt.Println(selamla("paket"))
}
merhaba, paket
DT5. birlikte.go, yardim.go’da tanımlanan selamla işlevini hiçbir içe aktarım
yazmadan çağırıyor. Bu derste ölçülen şey ikinci bir dosyanın varlığı değil — onu bu derse
kadar zaten kullanıyorduk — iki ayrı dosyanın tek bir ikili dosyaya derlendiğidir: go run . dizindeki bütün .go dosyalarını okur, aralarında içe aktarım aramaz, yalnız paket adının
eşleştiğini denetler.
Bu davranış, önceki ölçümdeki “hiçbiri eksik değil” satırını da baştan okumamızı gerektiriyor.
O satırda kullanılan tek dosyalık tamKaynak, derleme.gonun dışında, kendi başına bir
pakete yazılıp derlenmişti — orada sinif ve derlenirMiParcalar çağrılmıyordu, kaynak yalnız
package main ve boş bir func main taşıyordu. Burada ise birlikte.go, aynı dizindeki
yardim.goya bağımlı ve o bağımlılık dosya sınırını hiç aşan bir içe aktarım gerektirmiyor.
İki ölçüm birbirini tamamlıyor: biri paketin en küçük hâlini, öbürü paketin birden fazla
dosyaya yayılmış hâlini gösteriyor.
Bu, paket bildiriminin neden dosya başına yazıldığını da açıklıyor: derleyici, hangi
dosyaların birlikte bir bütün oluşturduğunu dosya adından değil, her dosyanın kendi ilk
satırından öğreniyor. derleme.go, yardim.go ve birlikte.go üçü de main yazıyor ve
üçü de aynı pakete katılıyor; herhangi biri farklı bir ad yazsaydı derleme aynı dizinde bile
dururdu.
Bu davranışın bir sonucu, içe aktarımların iki farklı düzeyde çalıştığıdır. birlikte.go,
fmt paketini kendi başına içe aktarıyor, çünkü standart kitaplığın bir parçasını kullanıyor;
ama yardim.goda tanımlanan selamlayı içe aktarmıyor, çünkü o zaten aynı paketin bir
parçası. İçe aktarım satırı yalnız paket sınırını geçen adlar için gerekiyor — aynı paketin
içinde, dosya sınırı bir anlam taşımıyor. Kursun paketler konusu bu sınırı tersine çevirecek:
orada dosyalar aynı pakette kalmaya devam edecek, ama bazı adlar paketin dışından hiç
görünmeyecek.
Sınırlayıcı Ölçüm: Modül Dosyası Kaynağın İçinde Değildir
Dördüncü satıra dönelim. modul dosyasi yok satırında kullanılan kaynak, ilk satırdakiyle
bayt bayt aynıdır — tamKaynak sabiti iki çağrıda da değişmeden geçiyor. Aradaki tek fark,
derlenirMiParcalar’a verilen modYaz bağımsız değişkeninin false olmasıdır: geçici dizine
go.mod hiç yazılmıyor.
DT6. Bu satırda ölçülen şey bir kaynak hatası değil, bir bağlam eksikliğidir. Kaynağın kendisinde paket bildirimi de giriş noktası da eksiksiz duruyor; derleyici yine de reddediyor, çünkü hangi modülün parçası olduğunu bilmeden bir dizini derlemeyi kabul etmiyor.
Bu satır, dersin ilk iki reddinden kalitatif olarak ayrılıyor. Paket bildirimini ya da giriş
noktasını kaldırırken kaynağa dokunuyorduk; burada kaynak hiç açılmadı bile, yalnız
dizindeki başka bir dosya silindi. Bir okuyucu, tamKaynak sabitine baksa, onun neden bazen
derlendiğini bazen derlenmediğini kaynağın kendisinden çıkaramaz — cevap kaynağın dışında,
dizinin içeriğinde duruyor.
Bunun pratik sonucu şu: bir kaynak dosyasını başka bir dizine kopyalamak, o dizinde bir modül dosyası yoksa hiçbir işe yaramaz — kaynak aynen kopyalanmış olsa bile derlenmez. Modül dosyası, paket bildirimi gibi kaynağın parçası değildir; kaynağın bağlamıdır. Bu ayrım önemlidir çünkü ikisi de “zorunlu” sayıldığı hâlde farklı bir şeye zorunludur: paket bildirimi kaynağın kendi kimliğini taşır, modül dosyası kaynağın nerede durduğunu bildirir. Kursun paketler konusu bu ayrımı derinleştirecek; burada ölçülen yalnız zorunluluğun kendisidir — modül dosyası olmadan hiçbir kaynak, ne kadar doğru yazılırsa yazılsın, bir ikili dosyaya dönüşmez.
Bu derste kullanılan ölçüm aracı, modül dosyasının içeriğini hiç değiştirmedi — yalnız var olup olmadığını değiştirdi. Bir modül dosyası, kendi başına, dışarıdan hiçbir bağımlılık istemeyen en küçük hâliyle bile zorunludur; içine hiçbir bağımlılık satırı eklenmemiş olması, derleyicinin modülü reddetmesi için bir neden değildir. Zorunlu olan modül dosyasının var olması, içeriğinin ne kadar dolu olduğu değil.
Modül dosyasının kaynaktan bağımsız olması, aynı kaynağın birden fazla modülün altına
taşınabileceği anlamına da geliyor: dizini değiştirip yeni bir go.mod yazmak, kaynağı hiç
değiştirmeden onu başka bir modülün parçası yapar. Paket bildirimini değiştirmek ise kaynağın
kendisini değiştirmek demektir. İkisi arasındaki bu fark, “zorunlu” sözcüğünün tek bir anlama
gelmediğini gösteriyor — bir şey kaynağın içinde zorunlu olabilir, bir şey kaynağın
etrafında zorunlu olabilir, ve ikisinin kaldırılma bedeli aynı değildir: biri kaynağı
bozar, öbürü yalnız kaynağın bulunduğu yeri geçersiz kılar.
Bu üçlü ayrım — kimlik, sözleşme, bağlam — ilk derste ölçülen dört rede de geriye dönük bir
çerçeve veriyor. Kullanılmayan değişken ve kullanılmayan içe aktarım, kaynağın kendi kimliğiyle
ilgiliydi: kaynakta yazılan her ad, kaynağın bir parçası olarak kalmayı hak etmeli. Yazılmayan
dönüş değeri bir sözleşme ihlaliydi: imza bir söz verir, gövde onu tutmalı. Bu derste eklenen
üçüncü kategori — bağlam — ilk derste hiç görünmedi, çünkü o dersteki dört kaynak zaten bir
modülün içine yazılıyordu. Zorunluluğun üç ayrı yerde durabileceğini görmek, kursun ilerleyen
derslerinde tekrar işe yarayacak: bir değişkenin sıfır değeri onun kimliğine, bir işlevin
adlandırılmış dönüşü onun sözleşmesine, bir paketin dışa aktarma kuralı ise onun
bağlamına ait olacak. Bu üç kategori bu kursta hiçbir yerde adıyla anılmayacak — burada
kurulan yalnız bir okuma alışkanlığı: bir derlenmedi yanıtı görüldüğünde ilk soru “hangi
satır eksik” değil, “eksik olan şey kaynağın neresinde duruyor” olmalı.
Özet
- Bir Go kaynağının derlenebilmesi için üç şey zorunludur: paket bildirimi, giriş noktası
(
func main), modül dosyası. - Üçü de eksikken derleme durur, ama üçü aynı nedenden durmaz: biri kaynağın kimliğini, biri sözleşmesini, biri bağlamını eksik bırakır.
- Aynı dizindeki iki dosya, aynı paket adını taşıdığı sürece içe aktarım yazmadan birbirini görür ve tek bir ikili dosyaya derlenir.
- Modül dosyası kaynağın parçası değil bağlamıdır: aynı kaynak, hiç değişmeden, modül dosyası kaldırıldığında derlenmez hâle gelir.
- Zorunluluk tek bir anlama gelmiyor — kaynağın içinde zorunlu olanla kaynağın etrafında zorunlu olan, kaldırıldığında farklı bir bedel ödetiyor.
Sonraki Adım
Bu derste bir kaynağın çalışabilmesi için gerekenler ölçüldü; hiçbir yerde bir değere ne
yazılıp ne yazılmadığı sorulmadı. func main() {} boş bir gövdeydi ve içinde hiçbir değişken
yoktu. Sıradaki ders bu boşluğu dolduruyor: bir değişkeni tanımlarken tipini yazmak zorunda
mısın, kısa bildirim tipi nereden çıkarıyor, ve sıralı bir sabit dizisinde yazılmayan değerleri
kim dolduruyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.