İçeriğe geç
academia.sh

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ırtamKaynak 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat