Tek bir main.go dosyasıyla başlayan her program er ya da geç büyür: fonksiyonlar çoğalır, tipler birbirine bağlanır, başkalarının yazdığı kütüphaneler projeye girer. Bu noktada üç soruya cevap vermen gerekir. Kodu hangi parçalara böleceksin? Her parça dışarıya neyi gösterip neyi saklayacak? Dışarıdan aldığın kodun hangi sürümünü kullandığını nasıl kayıt altına alacaksın?
Go bu soruları iki kavramla cevaplar: paket (package) ve modül (module). Bir halk kütüphanesi düşün. Kitaplar konulara göre raflara ayrılmıştır; bu raflar paketlerdir. Bazı raflar yalnızca personele açıktır; bunlar dışa aktarılmayan tanımlar ve internal dizinleridir. Binanın bir adresi ve bir kayıt defteri vardır; bunlar da modül yolu ve go.mod dosyasıdır. Başka bir kütüphaneden kitap ödünç aldığında hangi baskıyı aldığın deftere yazılır, kitabın yolda değiştirilmediği de bir mühürle doğrulanır. O mühür go.sum dosyasıdır.
Bu dersin sonunda paketlerini doğru adlandırıp düzenleyebilecek, neyi dışa aktaracağına bilinçli karar verebilecek, init fonksiyonlarının hangi sırayla çalıştığını açıklayabilecek, go.mod ve go.sum dosyalarını satır satır okuyabilecek, bağımlılıkları go get ve go mod tidy ile yönetebilecek ve birden fazla modülü go work ile yerelde birlikte geliştirebileceksin.
Paket, modül ve depo
Bu üç kavram sık karıştırılır, o yüzden önce sınırları çizelim:
- Paket, aynı dizindeki
.godosyalarından oluşan derleme birimidir. Aynı paketteki dosyalar birbirinin tanımlarını import etmeden kullanır; derleyici için hepsi tek bir bütündür. - Modül, kök dizininde
go.moddosyası bulunan ve birlikte sürümlenen paketler topluluğudur. Modül yolu (module path) hem modülün kimliğidir hem de içindeki bütün paketlerin import yollarının önekidir. - Depo (repository), kodun tutulduğu Git deposudur. Çoğu depo tek bir modül içerir, ama bir depoda birden fazla modül de bulunabilir.
Git deposu: github.com/ayse/banka
└── modül: github.com/ayse/banka ← kök dizinde go.mod
├── paket github.com/ayse/banka (kök dizin: main.go)
├── paket github.com/ayse/banka/account (account/ dizini)
│ ├── account.go
│ └── account_test.go
└── paket github.com/ayse/banka/internal/money
└── money.goStandart kütüphane paketlerinin (fmt, net/http, encoding/json) bir modül öneki yoktur. Bunlar Go araç zinciriyle birlikte gelir ve ayrıca indirilmez.
package bildirimi ve import
Paket adı ve import yolu
Her Go dosyası bir package bildirimiyle başlar. Bu satırla ilgili üç kuralı bilmen yeterli:
- Bir dizindeki bütün dosyalar aynı paket adını kullanır. Tek istisna, test dosyalarında kullanılabilen
_testsonekli dış test paketidir (bkz. Test ve Benchmark). package mainözeldir. Derleyici bu paketi çalıştırılabilir bir programa dönüştürür ve programın giriş noktasıfunc main()olur. Başka her paket adı bir kütüphane üretir.- Import yolu ile paket adı ayrı şeylerdir.
import "math/rand/v2"yazarsın ama koddarand.IntN(10)kullanırsın. Import yolu derleyiciye paketin nerede olduğunu söyler; kodda kullanacağın ad ise o dizindeki dosyalarınpackagesatırından gelir. Gelenek, paket adının dizinin son elemanıyla (sürüm soneki hariç) aynı olmasıdır.
import biçimleri
import bildiriminin birkaç biçimi var:
import (
// Standart import: fmt.Println diye kullanılır.
"fmt"
// Kendi modülündeki bir paket: account.New diye kullanılır.
"example.com/banka/account"
// Takma ad (alias): paket bu dosyada htmltemplate adıyla anılır.
htmltemplate "html/template"
// Boş import: paketin adı kullanılmaz, yalnızca başlatma kodu çalışır.
_ "image/png"
// Nokta import: Sqrt(2) yazılabilir. Okunabilirliği bozduğu için kaçın.
. "math"
)Takma ad en çok iki paketin adı çakıştığında işe yarar. text/template ve html/template paketlerinin ikisinin de adı template. Aynı dosyada ikisine birden ihtiyacın varsa birine takma ad verirsin:
package main
import (
htmltemplate "html/template"
"os"
"text/template"
)
func main() {
const page = "<p>Merhaba, {{.}}</p>\n"
name := "<b>Ayşe</b>"
// Metin şablonu veriyi olduğu gibi yazar.
t := template.Must(template.New("metin").Parse(page))
if err := t.Execute(os.Stdout, name); err != nil {
panic(err)
}
// HTML şablonu veriyi bağlama göre kaçışlar (escape).
h := htmltemplate.Must(htmltemplate.New("html").Parse(page))
if err := h.Execute(os.Stdout, name); err != nil {
panic(err)
}
}<p>Merhaba, <b>Ayşe</b></p> <p>Merhaba, <b>Ayşe</b></p>
İki şablon aynı metni işliyor ama sonuçlar farklı: html/template veriyi HTML bağlamına göre kaçışlıyor, böylece kullanıcıdan gelen <b> etiketi sayfada etiket olarak yorumlanmıyor. Takma ad olmasaydı iki import aynı template adını tanımlamaya çalışacak ve derleme hatası alacaktın.
Go'da kullanılmayan bir import da derleme hatasıdır ("os" imported and not used). Bu kural kodu temiz tutar ama elle takip etmesi yorucudur. Editöründeki gopls ya da goimports aracı importları otomatik ekler, siler ve gruplar.
Paket adlandırma kuralları
Paket adı, o paketi kullanan her satırda görünür: strings.Split, http.Get, json.Marshal. Bu yüzden iyi bir paket adı kısa, açık ve kullanıldığı yerde doğal okunan bir addır.
Bir tuzağa da dikkat et: bir değişkene paketle aynı adı verirsen o fonksiyonda paketi gölgelersin. url := "https://go.dev" yazdığın fonksiyonda artık net/url paketine erişemezsin. Değişkene rawURL ya da u gibi bir ad ver.
Büyük harf ile dışa aktarma
Go'da public ya da private gibi anahtar kelimeler yoktur. Görünürlüğü tanımlayıcının (identifier) ilk harfi belirler:
- Büyük harfle başlayan ad dışa aktarılır (exported): başka paketler onu kullanabilir.
- Küçük harfle ya da alt çizgiyle başlayan ad dışa aktarılmaz (unexported): yalnızca tanımlandığı paketin içinden görünür.
Kural istisnasızdır ve her şeye uygulanır: tipler, fonksiyonlar, metotlar, değişkenler, sabitler ve struct alanları. Derleyici aslında ilk karakterin Unicode'daki "büyük harf" kategorisinde olup olmadığına bakar; ama tanımlayıcılarını zaten İngilizce yazacağın için "ilk harf büyük mü?" diye düşünmen yeterli. Görünürlüğün sınırı dosya değil pakettir: account.go içindeki küçük harfli bir fonksiyonu aynı paketteki transfer.go rahatça çağırabilir.
Şimdi iki paketli küçük bir banka modülü kuralım. account paketi bir hesabı modelliyor, internal/money tutarları biçimlendiriyor, kök dizindeki main paketi de ikisini kullanıyor:
banka/
├── go.mod
├── main.go package main
├── account/
│ └── account.go package account
└── internal/
└── money/
└── money.go package moneymodule example.com/banka
go 1.26.2// Package account, basit bir banka hesabını modeller.
package account
import (
"errors"
"fmt"
)
// ErrInsufficientFunds, bakiye çekilmek istenen tutardan az olduğunda döner.
var ErrInsufficientFunds = errors.New("yetersiz bakiye")
// Account bir banka hesabıdır. Bakiye yalnızca metotlarla değiştirilebilir.
type Account struct {
Owner string // dışa aktarılır: her paket okuyup yazabilir
balance int // dışa aktarılmaz: yalnızca account paketi erişebilir
}
// New, verilen kişi adına sıfır bakiyeli bir hesap oluşturur.
func New(owner string) *Account {
return &Account{Owner: owner}
}
// Deposit hesaba para yatırır. Tutar pozitif olmalıdır.
func (a *Account) Deposit(amount int) error {
if err := validate(amount); err != nil {
return err
}
a.balance += amount
return nil
}
// Withdraw hesaptan para çeker. Bakiye yetmezse ErrInsufficientFunds döner.
func (a *Account) Withdraw(amount int) error {
if err := validate(amount); err != nil {
return err
}
if amount > a.balance {
return ErrInsufficientFunds
}
a.balance -= amount
return nil
}
// Balance güncel bakiyeyi döndürür.
func (a *Account) Balance() int {
return a.balance
}
// validate paketin iç yardımcısıdır; dışarıdan çağrılamaz.
func validate(amount int) error {
if amount <= 0 {
return fmt.Errorf("geçersiz tutar: %d", amount)
}
return nil
}// Package money, tutarları Türkçe yazım kurallarıyla biçimlendirir.
package money
import (
"strconv"
"strings"
)
// Format, 1250 değerini "1.250 TL" biçimine çevirir.
func Format(amount int) string {
sign := ""
if amount < 0 {
sign = "-"
amount = -amount
}
digits := strconv.Itoa(amount)
var b strings.Builder
for i, r := range digits {
if i > 0 && (len(digits)-i)%3 == 0 {
b.WriteByte('.')
}
b.WriteRune(r)
}
return sign + b.String() + " TL"
}package main
import (
"errors"
"fmt"
"log"
"example.com/banka/account"
"example.com/banka/internal/money"
)
func main() {
acc := account.New("Ayşe")
if err := acc.Deposit(1250); err != nil {
log.Fatal(err)
}
if err := acc.Withdraw(2000); errors.Is(err, account.ErrInsufficientFunds) {
fmt.Println("İşlem reddedildi:", err)
}
if err := acc.Deposit(-10); err != nil {
fmt.Println("İşlem reddedildi:", err)
}
fmt.Printf("%s: %s\n", acc.Owner, money.Format(acc.Balance()))
}Modülün kök dizininde programı çalıştıralım:
go run .İşlem reddedildi: yetersiz bakiye İşlem reddedildi: geçersiz tutar: -10 Ayşe: 1.250 TL
Neler olduğuna adım adım bakalım:
account.New,account.ErrInsufficientFunds,Accounttipi veOwneralanı büyük harfle başladığı içinmainpaketinden erişilebilir.balancealanı vevalidatefonksiyonu küçük harfle başlıyor; onlara yalnızcaaccountpaketi dokunabilir. Bakiyeyi değiştirmenin tek yoluDepositveWithdrawmetotları ve ikisi de tutarı doğruluyor. Böylece "bakiye negatif olamaz" kuralını paketin kendisi garanti ediyor; paketi kullanan kimse bu kuralı yanlışlıkla çiğneyemiyor.Balance()metodu alanı salt okunur biçimde dışarı açıyor. Go'da okuma metotlarınaGetBalancedeğil, doğrudanBalanceadı verilir.
Peki main paketi kuralı çiğnemeye çalışırsa ne olur?
package main
import (
"fmt"
"example.com/banka/account"
)
func main() {
acc := account.New("Ayşe")
acc.balance = 1_000_000
fmt.Println(account.validate(5))
}go build .# example.com/banka ./main.go:11:6: acc.balance undefined (type *account.Account has no field or method balance, but does have method Balance) ./main.go:12:22: name validate not exported by package account
Derleyici iki ihlali de yakalıyor, ilkinde yakın bir ad önererek (but does have method Balance) yardımcı da oluyor. Dışa aktarma bir öneri ya da lint kuralı değil, dilin kendisinin koyduğu bir sınırdır.
Neyi dışa aktarmalı?
Dışa aktardığın her ad, paketini kullananlara verdiğin bir sözdür. Bir fonksiyonu dışa aktarıp başkalarının onu kullanmasına izin verdikten sonra imzasını değiştirmek, onların kodunu bozmak demektir. Dışa aktarılmayan bir adı ise istediğin gibi değiştirebilir, silebilir, yeniden adlandırabilirsin. Bu yüzden iyi bir alışkanlık şudur: her şeyi küçük harfle başlat, yalnızca paketin kullanıcılarının gerçekten ihtiyaç duyduğu şeyleri büyük harfe çevir. Küçük bir API hem öğrenmesi kolay hem de bakımı ucuzdur.
init fonksiyonu
Bir paketin kullanıma hazır hale gelmesi iki adımda olur: önce paket düzeyindeki (package-level) değişkenlere ilk değerleri verilir, sonra init fonksiyonları çalışır. init fonksiyonunun kendine özgü kuralları var:
- Parametre almaz ve değer döndürmez:
func init(). - Kodun hiçbir yerinden çağrılamaz, adı bir değer olarak da kullanılamaz.
- Bir dosyada birden fazla, bir pakette istediğin kadar
initbulunabilir. - Paket kaç farklı yerden import edilirse edilsin başlatma işlemi bir kez yapılır.
- Bütün başlatma işlemleri tek bir goroutine'de, sırayla ve
mainfonksiyonundan önce yapılır.
Bir paketin içinde başlatma sırası
Paket düzeyi değişkenler yazılış sırasıyla değil, bağımlılık sırasıyla başlatılır: bir değişkenin ilk değeri başka bir değişkene bağlıysa önce o değişken hazırlanır. Bağımlılığı olmayan değişkenler yazıldıkları sırayı izler. Bütün değişkenler hazır olduktan sonra init fonksiyonları kaynak koddaki sıralarıyla çalışır. Paket birden çok dosyadan oluşuyorsa go aracı dosyaları adlarına göre sıralayıp derleyiciye verir; bu yüzden a.go dosyasındaki init, b.go dosyasındakinden önce çalışır.
package main
import "fmt"
// total ilk sırada yazıldı ama price ve tax'a bağlı olduğu için
// onlardan sonra hesaplanır.
var total = add(price, tax)
var price = trace("price", 100)
var tax = trace("tax", 18)
func trace(name string, v int) int {
fmt.Printf("değişken başlatılıyor: %s = %d\n", name, v)
return v
}
func add(a, b int) int {
fmt.Println("değişken başlatılıyor: total")
return a + b
}
func init() {
fmt.Println("birinci init, total =", total)
}
func init() {
fmt.Println("ikinci init")
}
func main() {
fmt.Println("main başladı")
}değişken başlatılıyor: price = 100 değişken başlatılıyor: tax = 18 değişken başlatılıyor: total birinci init, total = 118 ikinci init main başladı
total ilk sırada yazılmış olsa da price ve tax değişkenlerine bağlı olduğu için onlardan sonra hesaplandı. İki init fonksiyonu da bütün değişkenler hazır olduktan sonra, yazıldıkları sırayla çalıştı; main en son geldi.
Paketler arasında başlatma sırası
Paketler arasında tek bir temel kural vardır: bir paket, import ettiği bütün paketler başlatıldıktan sonra başlatılır. Aynı anda birden fazla paket başlatılmaya hazır olduğunda hangisinin önce geleceğini Go 1.21 ile kesinleştirilen bir algoritma belirler: paketler import yollarına göre alfabetik sıralanır; sonra listede, import ettiği her şey başlatılmış olan ilk paket seçilir, başlatılır ve listeden çıkarılır. Bu işlem liste boşalana kadar tekrarlanır.
Bunu gözle görmek için dört paketli bir modül kuralım: main paketi a ve b paketlerini, a da c paketini import ediyor. Her paket, değişkeni başlatılırken ve init fonksiyonu çalışırken ekrana bir satır yazıyor. Modülün yolu example.com/initsira; a, b ve c paketleri aynı adlı alt dizinlerde duruyor. b ve c, a ile aynı kalıpta yazıldı; tek farkları başka bir paket import etmemeleri.
package a
import (
"fmt"
"example.com/initsira/c"
)
var Ready = c.Ready && announce("a: paket düzeyi değişken")
func init() {
fmt.Println("a: init")
}
func announce(msg string) bool {
fmt.Println(msg)
return true
}package main
import (
"fmt"
"example.com/initsira/a"
"example.com/initsira/b"
)
var ready = a.Ready && b.Ready && announce("main: paket düzeyi değişken")
func init() {
fmt.Println("main: init")
}
func announce(msg string) bool {
fmt.Println(msg)
return true
}
func main() {
fmt.Println("main: başladı, ready =", ready)
}go run .b: paket düzeyi değişken b: init c: paket düzeyi değişken c: init a: paket düzeyi değişken a: init main: paket düzeyi değişken main: init main: başladı, ready = true
main.go içinde a paketi b'den önce import edildiği halde neden b en önce başladı? Algoritmayı adım adım uygulayalım (standart kütüphane paketlerini sadelik için dışarıda bırakıyoruz):
import grafiği import yoluna göre sıralı liste
main example.com/initsira (main) a ve b'yi bekliyor
/ \ example.com/initsira/a c'yi bekliyor
a b example.com/initsira/b hazır
| example.com/initsira/c hazır
c
1. tur: main bekliyor, a bekliyor, b hazır → b başlatılır
2. tur: main bekliyor, a bekliyor, c hazır → c başlatılır
3. tur: main bekliyor (a eksik), a hazır → a başlatılır
4. tur: main hazır → main başlatılır, sonra main() çalışırSıra, import satırlarının yazılış sırasına göre değil, bu algoritmaya göre belirlendi. Her durumda değişmeyen şey şu: a, kendisinden önce c'nin hazır olduğunu bilir; main de hem a'nın hem b'nin hazır olduğunu bilir.
init ne zaman kullanılır?
init iki durumda gerçekten işe yarar:
- Hazırlanması birkaç satır süren paket düzeyi veriler. Bir arama tablosunu doldurmak ya da bir tabloyu başka bir tablodan türetmek gibi.
- Kayıt (registration) deseni. Bir paket
initiçinde kendini başka bir pakete kaydeder. Örneğinimage/pngpaketi PNG çözücüsünüinitiçindeimagepaketine kaydeder. Bu yüzden programındapngadını hiç kullanmasan bile_ "image/png"boş importu,image.Decodefonksiyonunun PNG dosyalarını tanımasını sağlar.database/sqliçin yazılmış veritabanı sürücüleri de aynı yolla kaydolur.
Modüller: go.mod ve go.sum
go mod init
Yeni bir modül, boş bir dizinde go mod init example.com/banka komutuyla başlar. Komut, argüman olarak verdiğin modül yolunu ve kullandığın Go sürümünü içeren kısa bir go.mod dosyası oluşturur.
Modülünü başkalarının go get ile indirmesini istiyorsan modül yolu, kodun gerçekten bulunduğu depo adresi olmalıdır: github.com/ayse/banka gibi. Go aracı kodu bu yoldan yola çıkarak bulur. Yalnızca yerelde deneme yapıyorsan example.com/... biçiminde bir yol yeterli; example.com alan adı zaten örnekler için ayrılmıştır.
go.mod satır satır
Aşağıdaki go.mod, iki doğrudan bağımlılığı olan ve kod üretmek için stringer aracını kullanan bir modülden alındı:
module example.com/banka
go 1.26.2
require (
github.com/google/uuid v1.6.0
golang.org/x/text v0.42.0
)
require (
golang.org/x/mod v0.41.0 // indirect
golang.org/x/sync v0.23.0 // indirect
golang.org/x/tools v0.50.0 // indirect
)
tool golang.org/x/tools/cmd/stringerHer projede görmesen de tanıman gereken birkaç yönerge daha var:
go.sum
Aynı modülün go.sum dosyası şöyle:
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU=
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=Her satır üç parçadan oluşur: modül yolu, sürüm ve bir özet (hash). Her modül için iki satır görürsün. h1: ile başlayan ilk satır, o sürümdeki bütün dosyaların SHA-256 tabanlı özetidir. Sürümün sonuna /go.mod eklenmiş ikinci satır ise yalnızca o modülün go.mod dosyasının özetidir. İkincisi ayrı tutulur çünkü Go, bağımlılık grafiğini çözerken birçok modülün yalnızca go.mod dosyasını okur, kaynak kodunu hiç indirmez.
Bir modül sürümünü ilk kez indirdiğinde Go, hesapladığı özeti sum.golang.org sağlama toplamı veritabanıyla (checksum database) karşılaştırır. Biri bir sürüm etiketini taşıyıp içeriğini sessizce değiştirse bile bu sayede fark edilir. Sonraki derlemelerde yerel önbellekteki dosyalar da go.sum ile doğrulanır. Şirket içi, herkese açık olmayan modüllerin bu veritabanına sorulmaması için GOPRIVATE ortam değişkenini kullanırsın (GOPRIVATE=github.com/sirketim/*).
Minimal sürüm seçimi (MVS)
require satırındaki sürüm "tam olarak bu" değil, "en az bu" anlamına gelir. Birden fazla modül aynı bağımlılığın farklı sürümlerini isteyebilir. Go bu durumda Minimal Sürüm Seçimi (Minimal Version Selection, MVS) algoritmasını kullanır: her bağımlılık için, gereksinim grafiğinde istenen sürümlerin en büyüğünü seçer. Yayımlanmış en yeni sürüme bakmaz.
senin modülün ──require──▶ A v1.2.0
│
└──require──▶ B v1.0.0 ──require──▶ A v1.4.0
A'nın yayımlanmış sürümleri: v1.2.0 v1.3.0 v1.4.0 v1.5.0 v1.7.0
▲
seçilen: istenenlerin en büyüğü = v1.4.0Senin modülün "en az v1.2.0", B ise "en az v1.4.0" diyor. İkisini de mutlu eden en küçük sürüm v1.4.0. v1.7.0 yayımlanmış olsa da kimse onu istemediği için seçilmiyor. Bu yaklaşımın güzel yanı öngörülebilir olmasıdır: bir kütüphane yazarı bugün yeni bir sürüm yayımladığında senin derlemen yarın kendiliğinden değişmez. Yükseltme her zaman senin bilinçli bir go get komutunla olur.
go get, go mod tidy ve sürümleme
Anlamsal sürümleme
Go modülleri anlamsal sürümleme (semantic versioning, kısaca semver) kullanır. Bir sürüm vMAJOR.MINOR.PATCH biçimindedir:
v0.x.ysürümleri kararsız kabul edilir; her minor sürümde API değişebilir.v2.0.0-rc.1gibi ön sürümler (pre-release), kararlı bir sürüm varken@latestile seçilmez.- Etiketlenmemiş bir commit'e bağımlı olursan Go bir sahte sürüm (pseudo-version) üretir:
v0.0.0-20260105143000-1a2b3c4d5e6f. Bu dizge bir taban sürüm, UTC zaman damgası ve commit özetinin ilk 12 karakterinden oluşur.
Bağımlılık ekleme ve güncelleme
Gerçek bir örnek üzerinden gidelim. Türkçe kelimeleri sıralamak istiyoruz. slices.Sort dizgeleri byte değerlerine göre sıralar; ç, ı, ö, ş, ü gibi harflerin UTF-8 kodları ASCII harflerinden büyük olduğu için bu harflerle başlayan kelimeler en sona düşer. Doğru alfabetik sıra için Go ekibinin bakımını yaptığı golang.org/x/text modülündeki collate paketini kullanacağız:
package main
import (
"fmt"
"slices"
"golang.org/x/text/collate"
"golang.org/x/text/language"
)
func main() {
words := []string{"zeytin", "şeftali", "çilek", "ıhlamur", "incir", "ördek", "elma", "üzüm"}
byteOrder := slices.Clone(words)
slices.Sort(byteOrder)
fmt.Println("slices.Sort:", byteOrder)
c := collate.New(language.Turkish)
c.SortStrings(words)
fmt.Println("collate :", words)
}Bağımlılığı önce bilerek eski bir sürümle ekleyelim, sonra en yeni sürüme yükseltelim:
go get golang.org/x/[email protected]
go get golang.org/x/text@latestgo: downloading golang.org/x/text v0.20.0 go: added golang.org/x/text v0.20.0 go: downloading golang.org/x/text v0.42.0 go: upgraded golang.org/x/text v0.20.0 => v0.42.0
go.mod dosyasına bakınca bir tuhaflık görürsün:
module example.com/sirala
go 1.26.2
require golang.org/x/text v0.42.0 // indirectmain.go bu modülü doğrudan import ettiği halde satırda // indirect yazıyor. go get bağımlılığı ekler ama işaretlemeyi her zaman doğru yapmaz. Modül dosyasını kodla uyumlu hale getiren komut go mod tidy:
go mod tidy
cat go.mod
go run .module example.com/sirala go 1.26.2 require golang.org/x/text v0.42.0 slices.Sort: [elma incir zeytin çilek ördek üzüm ıhlamur şeftali] collate : [çilek elma ıhlamur incir ördek şeftali üzüm zeytin]
go mod tidy, modüldeki bütün paketleri (testler ve farklı işletim sistemleri için olan dosyalar dahil) tarar. Eksik gereksinimleri ekler, artık kullanılmayanları siler, // indirect işaretlerini düzeltir ve go.sum dosyasını günceller. Bir değişikliği commit etmeden önce çalıştırmayı alışkanlık haline getir. CI tarafında go mod tidy -diff komutu dosyaları değiştirmeden farkı gösterir ve düzenlenecek bir şey varsa hata koduyla çıkar.
go get komutunun sık kullanılan biçimleri şunlar:
Modülleri incelemek için de şu komutları kullanırsın:
Büyük sürümler ve /v2 import yolu
Go'nun sürümleme sistemi, import uyumluluk kuralına (import compatibility rule) dayanır: eski bir paketle yeni bir paket aynı import yolunu paylaşıyorsa, yeni paket eskisiyle geriye uyumlu olmalıdır. Major sürüm ise tanımı gereği uyumluluğu bozar. O halde yeni major sürümün yeni bir import yolu olmalıdır. Buna anlamsal import sürümleme (semantic import versioning) denir.
Pratikte v2 ve sonrasında modül yolunun sonuna major sürüm eklenir:
// para modülünün go.mod dosyasındaki satır:
// module github.com/ayse/para/v2
//
// Bu modülü kullanan kod:
import "github.com/ayse/para/v2" // kodda yine para.Format diye kullanılırKazanç büyük: github.com/ayse/para ile github.com/ayse/para/v2 Go'nun gözünde iki ayrı modüldür ve aynı programda yan yana kullanılabilir. Büyük bir projede v2'ye tek seferde değil, paket paket geçebilirsin. Standart kütüphane de bu yolu izledi: Go 1.22 ile gelen math/rand/v2, eski math/rand paketini bozmadan yanına eklendi.
Bir v2 modülünü iki şekilde düzenleyebilirsin: ya depo kökündeki go.mod dosyasının module satırını /v2 ile bitirip v2.0.0 etiketi atarsın, ya da deponda kendi go.mod dosyası olan bir v2/ alt dizini açarsın. v0 ve v1 sürümleri sonek almaz. Kendi go.mod dosyası olan bir modül, /v2 soneki olmadan v2 etiketi yayımlarsa Go bu sürümü kabul etmez. Modül sistemi öncesinden kalan, go.mod dosyası olmayan eski depolardaki v2 ve üstü etiketler ise v2.3.0+incompatible gibi görünür.
internal paketleri
Dışa aktarma kuralı iki kademe sunar: paketin içi ve bütün dünya. Oysa büyük bir modülde çoğu zaman üçüncü bir kademe istersin: "modülümdeki paketler kullanabilsin ama dışarıdakiler kullanamasın". Bunun için yolunda internal adlı bir dizin bulunan paketler kullanılır.
Kural şudur: .../a/b/internal/c yolundaki bir paketi yalnızca .../a/b dizininin altındaki paketler import edebilir.
example.com/banka/
├── main.go ✓ internal/money'i import edebilir
├── account/
│ ├── account.go ✓ internal/money'i import edebilir
│ └── internal/rates/ yalnızca account/ altındakiler kullanabilir
│ (kök dizindeki main.go bile kullanamaz)
└── internal/
└── money/money.go
example.com/baska/main.go ✗ example.com/banka/internal/money yasakBaşka bir modülden internal/money paketini import etmeye çalışalım:
package main
import (
"fmt"
"example.com/banka/internal/money"
)
func main() {
fmt.Println(money.Format(5000))
}go build ./baskapackage example.com/baska baska/main.go:6:2: use of internal package example.com/banka/internal/money not allowed
Bu kuralı go aracı uygular; bir gelenek ya da uyarı değil, derlemeyi durduran bir hatadır. internal dizinleri sayesinde modülünün dışarıya açık yüzeyini küçük tutarken, iç paketlerini birbirleriyle serbestçe paylaşabilir ve istediğin zaman yeniden düzenleyebilirsin. Kimse onlara bağımlı olamayacağı için değiştirmek kimsenin kodunu bozmaz. internal dizini yolun herhangi bir derinliğinde bulunabilir; sınır her zaman internal dizininin bir üst dizinidir.
Proje düzeni
Go projeleri için dayatılmış tek bir dizin yapısı yoktur ve küçük başlamak en iyisidir. Tek bir paketten oluşan bir araç için kök dizinde go.mod ve birkaç .go dosyası yeter. Proje büyüdükçe şu yapı yaygın ve kullanışlıdır:
banka/
├── go.mod
├── go.sum
├── cmd/
│ ├── banka-api/
│ │ └── main.go HTTP sunucusu (package main)
│ └── banka-cli/
│ └── main.go komut satırı aracı (package main)
├── internal/
│ ├── account/ iş kuralları
│ ├── money/
│ └── storage/ veritabanı erişimi
└── testdata/ testlerin okuduğu dosyalarcmd/altındaki her dizin ayrı birpackage mainprogramıdır.go buildvego install, çalıştırılabilir dosyaya dizinin adını verir:cmd/banka-clidizinindenbanka-cliprogramı çıkar.internal/altındaki paketler uygulamanın kendisidir ve dışarıya kapalıdır.- Başkalarının import etmesi için yazdığın bir kütüphanede ise ana paketi modülün kök dizinine koyarsın:
github.com/ayse/slugmodülünün kökündeslug.gobulunur, kullanıcılarslug.Make(...)yazar. - Paketlere tiplere göre (
models/,controllers/,services/) değil, sorumluluklara göre (account,money,storage) ayırmak Go'da daha doğal sonuç verir. Birpkg/dizini de zorunlu değildir; Go ekibinin rehberi için go.dev/doc/modules/layout sayfasına bakabilirsin.
Bu yapıyla çalışırken sık kullanacağın komutlar:
go build ./... # modüldeki bütün paketleri derle
go test ./... # bütün paketlerin testlerini çalıştır
go vet ./... # bütün paketleri statik analizden geçir
go run ./cmd/banka-cli # tek bir programı derleyip çalıştır
go install ./cmd/banka-cli # programı $(go env GOPATH)/bin altına kur./... deseni "bu dizin ve altındaki bütün paketler" demektir. go install, derlenen programı GOBIN ortam değişkeninin gösterdiği dizine, o tanımlı değilse $(go env GOPATH)/bin altına koyar; bu dizini PATH değişkenine eklersen programı her yerden çalıştırabilirsin. Başkasının yazdığı bir aracı kurmak için sürümle birlikte tam yolunu verirsin: go install golang.org/x/tools/cmd/stringer@latest. Sürümlü go install bulunduğun dizindeki go.mod dosyasını yok sayar ve modülünü değiştirmez.
go work ile çoklu modül geliştirme
Diyelim ki iki modül üzerinde aynı anda çalışıyorsun: example.com/metin adında bir kütüphane ve onu kullanan example.com/uygulama. Kütüphaneye yeni bir fonksiyon ekledin ve uygulamada hemen denemek istiyorsun, ama kütüphaneyi henüz yayımlamadın. Uygulamanın dizininde çalıştırınca Go modülü bulamaz:
calisma/
├── metin/
│ ├── go.mod module example.com/metin
│ └── metin.go
└── uygulama/
├── go.mod module example.com/uygulama
└── main.go// Package metin, Türkçe metinler için yardımcı fonksiyonlar sunar.
package metin
import (
"strings"
"unicode"
"unicode/utf8"
)
// Title her kelimenin ilk harfini Türkçe kurallarına göre büyütür.
func Title(s string) string {
words := strings.Fields(s)
for i, w := range words {
r, size := utf8.DecodeRuneInString(w)
words[i] = string(unicode.TurkishCase.ToUpper(r)) + w[size:]
}
return strings.Join(words, " ")
}package main
import (
"fmt"
"example.com/metin"
)
func main() {
fmt.Println(metin.Title("ılık bir istanbul akşamı"))
}cd uygulama
go run .main.go:6:2: no required module provides package example.com/metin; to add it: go get example.com/metin
Eskiden bu sorun go.mod dosyasına replace example.com/metin => ../metin satırı eklenerek çözülürdü. İşe yarar, ama o satırı commit etmeyi unutmazsan başkalarının derlemesi bozulur. Go 1.18 ile gelen çalışma alanları (workspaces) bu işi modül dosyalarına dokunmadan yapar:
cd ..
go work init ./metin ./uygulama
cat go.work
go run ./uygulamago 1.26.2 use ( ./metin ./uygulama ) Ilık Bir İstanbul Akşamı
go.work dosyası, listelenen modüllerin hepsini aynı anda "ana modül" olarak kullanır. uygulama artık example.com/metin paketini doğrudan yan dizinden alıyor; ne go get ne de replace gerekti. Bu dosyanın bulunduğu dizinde ya da alt dizinlerinde çalıştırdığın her go komutu çalışma alanı modunda çalışır.
Belgelendirme: go doc ve pkg.go.dev
Go'da belgeler ayrı bir dosyada değil, kodun hemen üstündeki yorumlarda yaşar. Bir tanımın hemen önündeki yorum onun belge yorumudur (doc comment). Gelenekler basit:
- Yorum, tanımlanan adla başlayan tam bir cümledir:
// Withdraw hesaptan para çeker. - Paket yorumu
// Package account, ...diye başlar vepackagesatırının hemen üstüne yazılır. Uzun paket belgeleri içindoc.goadında ayrı bir dosya açmak yaygındır. - Belge yorumlarında başlık (
# Başlık), liste ve[account.New]biçiminde tanımlara bağlantı kullanılabilir.
go doc komutu bu yorumları terminalde gösterir. Banka modülünün kök dizininde:
go doc ./account
go doc ./account Account.Withdrawpackage account // import "example.com/banka/account"
Package account, basit bir banka hesabını modeller.
var ErrInsufficientFunds = errors.New("yetersiz bakiye")
type Account struct{ ... }
func New(owner string) *Account
package account // import "example.com/banka/account"
func (a *Account) Withdraw(amount int) error
Withdraw hesaptan para çeker. Bakiye yetmezse ErrInsufficientFunds döner.Dikkat edersen validate fonksiyonu ve balance alanı listede yok: go doc varsayılan olarak yalnızca dışa aktarılmış adları gösterir (-u bayrağı hepsini gösterir). Birkaç kullanışlı biçim daha: go doc fmt.Println tek bir fonksiyonu, go doc -all net/http bir paketin bütün belgesini, go doc -src strings.Cut kaynak kodu gösterir.
Herkese açık modüllerin belgeleri pkg.go.dev sitesinde yayımlanır. Bir modül sürümü Go modül vekil sunucusu (proxy.golang.org) üzerinden bir kez istendiğinde site onu kendiliğinden işler. Sayfada belgelerin yanında modülün sürümleri, lisansı, bağımlılıkları ve onu import eden paketler de görünür. Test dosyalarına yazdığın Example fonksiyonları da burada çalıştırılabilir örnek olarak listelenir.
Sık yapılan hatalar
- Döngüsel import.
xpaketiy'yi,ydex'i import ederse derlemeimport cycle not allowedhatasıyla durur. Go döngüsel bağımlılıklara izin vermez. Ortak tipleri üçüncü bir pakete taşı ya da bağımlılığı bir arayüzle ters çevir: arayüzü, onu kullanan pakette tanımla. - Dizin adıyla paket adının farklı olması.
money/dizinindepackage parayazmak derlenir ama import eden herkesi şaşırtır; import satırımoneyderken koddapara.Formatgörünür. Takma ad kullanmıyorsan paket adını dizin adıyla aynı tut. - Her şeyi
utilsya dacommonpaketine koymak. Bu paketler zamanla herkesin bağımlı olduğu, kimsenin sahiplenmediği yığınlara dönüşür ve döngüsel import sorunlarına davetiye çıkarır. Kodu ne yaptığına göre adlandırılmış küçük paketlere böl. - JSON'da küçük harfli alanların görünmesini beklemek. Dışa aktarılmayan alanlar yansıma kullanan paketler için görünmezdir.
go.moddosyasını elle düzenleyipgo mod tidyçalıştırmamak ya dago.sumdosyasını commit etmemek. Başkasının makinesinde derlememissing go.sum entryhatasıyla durur.- v2 yayımlarken
/v2sonekini unutmak. Major sürümü yükselttiğinde modül yolunu ve kendi içindeki bütün importları da güncellemen gerekir. - Modül yolunun depo adresiyle uyuşmaması.
go.modiçindemodule bankayazıp kodugithub.com/ayse/bankaadresinde yayımlarsan,go getbu modülü indirirkenmodule declares its path asdiye başlayan bir hatayla reddeder. - Yerel
replacesatırlarını commit etmek.replace example.com/metin => ../metinsenin makinende çalışır, başkasınınkinde çalışmaz. Yerel geliştirme içingo workkullan. initfonksiyonuna iş yüklemek. Hata döndüremeyen, test edilmesi zor ve her import edende çalışan gizli kod yazmış olursun.
Alıştırmalar
Aynı programda hem eski math/rand hem de yeni math/rand/v2 paketini kullan. İki paketin de adı rand olduğu için birine takma ad vermen gerekecek. Her paketten sabit tohumlu (seed) bir üreteç oluştur ve 0 ile 99 arasında üçer sayı yazdır. Eski pakette tohum olarak 42, yeni pakette rand.NewPCG(1, 2) kullan.
İpucu
Eski paket rand.New(rand.NewSource(42)) ve Intn metodunu, yeni paket rand.New(rand.NewPCG(1, 2)) ve IntN metodunu kullanır. Takma adı import satırında, yolun önüne yazarsın.
Çözümü göster
package main
import (
"fmt"
randv1 "math/rand"
"math/rand/v2"
)
func main() {
old := randv1.New(randv1.NewSource(42))
fmt.Println("math/rand :", old.Intn(100), old.Intn(100), old.Intn(100))
r := rand.New(rand.NewPCG(1, 2))
fmt.Println("math/rand/v2:", r.IntN(100), r.IntN(100), r.IntN(100))
}math/rand : 5 87 68 math/rand/v2: 76 61 78
math/rand/v2 farklı bir import yoluna sahip olduğu için Go'nun gözünde tamamen ayrı bir pakettir; iki sürüm aynı programda sorunsuzca yan yana durur. Paket adları çakıştığı için eski pakete randv1 takma adını verdik. Metot adlarındaki fark (Intn ve IntN), major sürüm yükseltmesinin neden yeni bir import yolu gerektirdiğini gösteriyor: v2 geriye uyumsuz API değişiklikleri içeriyor.
Aşağıdaki program işlemleri bir haritaya kaydediyor ve kaç işlem kayıtlı olduğunu yazdırıyor. Çıktıda kayıtlı işlem sayısı: 2 görmeyi bekliyoruz ama program 0 yazdırıyor. Nedenini açıkla ve summary değişkenini paket düzeyinde bırakarak programı düzelt.
package main
import "fmt"
var operations = map[string]func(a, b int) int{}
var summary = fmt.Sprintf("kayıtlı işlem sayısı: %d", len(operations))
func init() {
operations["topla"] = func(a, b int) int { return a + b }
operations["çarp"] = func(a, b int) int { return a * b }
}
func main() {
fmt.Println(summary)
fmt.Println("topla(3, 4) =", operations["topla"](3, 4))
}kayıtlı işlem sayısı: 0 topla(3, 4) = 7
İpucu
Paket düzeyi değişkenlerin hepsi, herhangi bir init fonksiyonu çalışmadan önce başlatılır. summary hesaplandığı anda harita hâlâ boştur. Haritayı doldurma işini değişken başlatmanın bir parçası yaparsan bağımlılık sırası işini görür.
Çözümü göster
package main
import "fmt"
// summary, operations'a bağlı olduğu için yukarıda yazılmış olsa da
// operations hazırlandıktan sonra hesaplanır.
var summary = fmt.Sprintf("kayıtlı işlem sayısı: %d", len(operations))
var operations = map[string]func(a, b int) int{
"topla": func(a, b int) int { return a + b },
"çarp": func(a, b int) int { return a * b },
}
func main() {
fmt.Println(summary)
fmt.Println("topla(3, 4) =", operations["topla"](3, 4))
}kayıtlı işlem sayısı: 2 topla(3, 4) = 7
Hatalı programda sıra şuydu: önce operations boş bir harita olarak, sonra summary o boş haritanın uzunluğuyla (0) başlatıldı; init haritayı ancak bunlardan sonra doldurdu. Düzeltmede kayıtları haritanın bileşik değişmezine (composite literal) taşıdık. Artık summary, dolu haritaya bağlı bir değişken; derleyici bağımlılığı gördüğü için summary değişkenini, yukarıda yazılmış olsa bile operations hazır olduktan sonra hesaplıyor. Diğer bir çözüm, summary değerini init içinde, kayıtlardan sonra atamak olurdu.
example.com/sayac adında bir modül oluştur. Modülde iki paket olsun:
internal/counter: kelime frekanslarını tutan birCountertipi. Haritası dışa aktarılmasın.Add(word string),Count(word string) intve en sık geçennkelimeyi döndürenTop(n int) []stringmetotları olsun (eşitlikte alfabetik sıra).Countertipinin sıfır değeri, yapıcı fonksiyon çağırmadan kullanılabilmeli.cmd/sayac: bir cümledeki kelimeleri sayıp sonuçları yazdıran program.
Her dışa aktarılmış ada belge yorumu yaz ve go doc ile kontrol et.
İpucu
Sıfır değerin kullanılabilir olması için Add içinde harita nil ise önce make ile oluştur. nil bir haritadan okumak güvenlidir, yazmak panik oluşturur. Sıralama için slices.SortFunc ve cmp.Or ile iki ölçütü birleştirebilirsin.
Çözümü göster
sayac/
├── go.mod
├── cmd/
│ └── sayac/
│ └── main.go
└── internal/
└── counter/
└── counter.go// Package counter, kelimelerin bir metinde kaç kez geçtiğini sayar.
package counter
import (
"cmp"
"maps"
"slices"
)
// Counter kelime frekanslarını tutar. Sıfır değeri kullanıma hazırdır.
type Counter struct {
counts map[string]int
}
// Add kelimenin sayacını bir artırır.
func (c *Counter) Add(word string) {
if c.counts == nil {
c.counts = make(map[string]int)
}
c.counts[word]++
}
// Count kelimenin kaç kez eklendiğini döndürür.
func (c *Counter) Count(word string) int {
return c.counts[word]
}
// Top en sık geçen n kelimeyi döndürür; eşitlikte alfabetik sıra kullanılır.
func (c *Counter) Top(n int) []string {
words := slices.Collect(maps.Keys(c.counts))
slices.SortFunc(words, func(a, b string) int {
return cmp.Or(cmp.Compare(c.counts[b], c.counts[a]), cmp.Compare(a, b))
})
return words[:min(n, len(words))]
}package main
import (
"fmt"
"strings"
"example.com/sayac/internal/counter"
)
func main() {
text := "bir berber bir berbere gel beraber bir berber dükkânı açalım demiş"
var c counter.Counter // sıfır değeri doğrudan kullanılabilir
for _, w := range strings.Fields(text) {
c.Add(w)
}
fmt.Println("bir:", c.Count("bir"))
fmt.Println("berber:", c.Count("berber"))
fmt.Println("en sık 3:", c.Top(3))
}go vet ./...
go run ./cmd/sayac
go doc ./internal/counterbir: 3
berber: 2
en sık 3: [bir berber açalım]
package counter // import "example.com/sayac/internal/counter"
Package counter, kelimelerin bir metinde kaç kez geçtiğini sayar.
type Counter struct{ ... }counts alanı küçük harfle başladığı için paket dışından okunamaz ya da değiştirilemez; bütün erişim metotlardan geçer. Add metodu haritayı ilk kullanımda oluşturduğu için var c counter.Counter yazmak yeterli. Standart kütüphanedeki strings.Builder ve sync.Mutex de sıfır değeri kullanılabilir tiplerdir. Top, önce frekansa göre azalan, eşitlikte kelimeye göre artan sırada sıralıyor; cmp.Or ilk sıfır olmayan karşılaştırma sonucunu döndürür. Paket internal altında olduğu için yalnızca example.com/sayac modülündeki kod onu import edebilir.
Bir çalışma alanında üç modül oluştur:
example.com/selam(v1):Hello(name string) stringfonksiyonu Türkçe selam döndürsün.example.com/selam/v2:Hello(name, lang string) (string, error)fonksiyonutr,envededillerini desteklesin, bilinmeyen dilde hata döndürsün. İmza v1 ile uyumsuz olduğu için bu bir major sürüm.example.com/app: iki sürümü aynı programda kullansın.
v2 modülünü selam/v2 alt dizinine koy ve üç modülü go work ile birbirine bağla.
İpucu
selam/v2 dizininin kendi go.mod dosyası olmalı ve module satırı /v2 ile bitmeli. İki paketin adı da selam olacağı için app içinde birine takma ad ver. Çalışma alanını go work init ./app ./selam ./selam/v2 ile kurabilirsin.
Çözümü göster
surum/
├── go.work
├── app/
│ ├── go.mod module example.com/app
│ └── main.go
└── selam/
├── go.mod module example.com/selam
├── selam.go
└── v2/
├── go.mod module example.com/selam/v2
└── selam.go// Package selam, selamlama metinleri üretir.
package selam
// Hello Türkçe bir selam döndürür.
func Hello(name string) string {
return "Merhaba, " + name + "!"
}// Package selam, birden çok dilde selamlama metinleri üretir.
package selam
import "fmt"
var greetings = map[string]string{
"tr": "Merhaba",
"en": "Hello",
"de": "Hallo",
}
// Hello verilen dilde bir selam döndürür. v1'deki imzayla uyumlu değildir.
func Hello(name, lang string) (string, error) {
g, ok := greetings[lang]
if !ok {
return "", fmt.Errorf("desteklenmeyen dil: %q", lang)
}
return g + ", " + name + "!", nil
}package main
import (
"fmt"
"example.com/selam"
selamv2 "example.com/selam/v2"
)
func main() {
fmt.Println(selam.Hello("Deniz"))
for _, lang := range []string{"en", "de", "fr"} {
msg, err := selamv2.Hello("Deniz", lang)
if err != nil {
fmt.Println("hata:", err)
continue
}
fmt.Println(msg)
}
}go work init ./app ./selam ./selam/v2
cat go.work
go run ./appgo 1.26.2 use ( ./app ./selam ./selam/v2 ) Merhaba, Deniz! Hello, Deniz! Hallo, Deniz! hata: desteklenmeyen dil: "fr"
selam/v2 dizini kendi go.mod dosyasına sahip olduğu için üst dizindeki example.com/selam modülüne dahil değil; ayrı bir modül. İki modülün import yolları farklı olduğundan (example.com/selam ve example.com/selam/v2), Go ikisini aynı derlemede sorunsuzca kullanıyor. Paket adları aynı olduğu için v2'ye selamv2 takma adını verdik. Gerçek bir projede bu modüller yayımlandığında go.work dosyasına gerek kalmaz; app iki sürümü de go get ile ekler.
Kısa sınav
city paketinde tanımlanan adlardan hangisine main paketinden city.Ad biçiminde erişilebilir?
Bir pakette var x = y * 2 ve ondan sonra var y = 5 tanımlı. init fonksiyonu fmt.Println(x) çalıştırırsa ne yazdırılır?
example.com/shop/internal/payment paketini aşağıdakilerden hangisi import edebilir?
Modülün A v1.2.0 istiyor, bağımlılıklarından B ise A v1.4.0 istiyor. A modülünün yayımlanmış en yeni sürümü v1.7.0. Derlemede hangi sürüm kullanılır?
go.mod dosyası module github.com/ali/para/v2 diyen bir modülün kök paketini nasıl import edersin ve kodda hangi adla kullanırsın?
go.sum dosyasının asıl görevi nedir?
Özet
- Paket aynı dizindeki dosyalardan oluşan derleme birimidir; modül kökünde
go.modbulunan ve birlikte sürümlenen paketler topluluğudur. Import yolu, modül yolu ile dizin yolunun birleşimidir. - Büyük harfle başlayan adlar dışa aktarılır, diğerleri paketin içinde kalır. Görünürlüğün sınırı dosya değil pakettir. Dışa aktarılmayan alanlar, paketin kendi kurallarını korumasını sağlar.
- Paket düzeyi değişkenler bağımlılık sırasıyla başlatılır, ardından
initfonksiyonları çalışır. Bir paket, import ettiği paketlerden sonra başlatılır; kardeş paketlerin sırasına güvenme veinitiçine ağır iş koyma. go.modmodül yolunu, en düşük Go sürümünü ve bağımlılıkların en düşük sürümlerini tutar.go.sumindirilen içeriğin bütünlüğünü doğrular. İkisi de commit edilir.- Minimal sürüm seçimi, istenen sürümlerin en büyüğünü seçer; derlemen kendiliğinden değişmez. Yükseltmeyi
go get paket@sürümile sen yaparsın,go mod tidyile dosyaları kodla uyumlu tutarsın. - v2 ve sonrası için modül yolu
/v2gibi bir sonek alır; farklı major sürümler aynı programda birlikte kullanılabilir. internaldizinindeki paketleri yalnızca üst dizinin altındaki kod import edebilir. Uygulamalardacmd/veinternal/düzeni yaygındır.- Yayımlanmamış modülleri yerelde birlikte geliştirmek için
replaceyerinego workkullan; belgelerigo docile terminalde, pkg.go.dev üzerinde de tarayıcıda okursun.