08 Nisan 2025

Go Programlamayı Öğrenin: Başlangıç Kılavuzu

Büyük, hızlı ve güvenilir yazılımlar oluşturmak genellikle karmaşıklığı yönetmek gibi hissettirir. Peki ya bunu basitleştirmek için sıfırdan tasarlanmış, hızı ve anlaşılır eşzamanlılığı karmaşaya boğulmadan sunan bir dil olsaydı? Karşınızda Go (genellikle Golang olarak da adlandırılır), modern yazılım geliştirmenin zorluklarına, özellikle de büyük ölçekte, doğrudan çözüm bulmak için tasarlanmış bir programlama dili. Basitliği, verimliliği ve eşzamanlı programlamayı önceliklendirerek geliştiricileri son derece üretken kılmayı hedefler. Bu Go eğitimi, Go programlamayı öğrenmek için gereken temel kavramlarda size yol göstererek başlangıç noktanız olarak hizmet eder.

Go Nedir?

Go, Google bünyesinde 2007 civarında ortaya çıktı. Sistem programlama alanında deneyimli mühendisler tarafından, hayran oldukları dillerin en iyi yönlerini birleştirirken sevmedikleri karmaşıklıkları (özellikle C++'da bulunanları) atmak amacıyla tasarlandı. 2009'da kamuoyuna duyurulan ve 2012'de kararlı 1.0 sürümüne ulaşan Go, yazılım geliştirme topluluğunda hızla ilgi gördü.

Go'yu tanımlayan temel özellikler şunlardır:

  • Statik Tipli: Değişken türleri kod derlendiğinde kontrol edilir, bu da birçok hatayı erken yakalar. Go, akıllıca tip çıkarımı kullanarak birçok durumda açık tip bildirimlerine olan ihtiyacı azaltır.
  • Derlenmiş: Go kodu doğrudan makine koduna derlenir. Bu, tipik dağıtımlar için bir yorumlayıcıya veya sanal makineye ihtiyaç duymadan hızlı yürütme hızları sağlar.
  • Çöp Toplamalı: Go, bellek yönetimini otomatik olarak yapar, geliştiricileri diğer dillerde yaygın bir hata kaynağı olan manuel bellek ayırma ve serbest bırakma karmaşıklığından kurtarır.
  • Dahili Eşzamanlılık: Go, İletişim Kuran Sıralı Süreçler'den (CSP) esinlenerek hafif goroutine'ler ve kanallar kullanarak eşzamanlılık için birinci sınıf destek sağlar. Bu, aynı anda birden fazla görevi yerine getiren programlar oluşturmayı çok daha yönetilebilir hale getirir.
  • Kapsamlı Standart Kütüphane: Go, ağ oluşturma, dosya G/Ç, veri kodlama/kod çözme (JSON gibi), kriptografi ve test etme gibi yaygın görevler için sağlam paketler sunan zengin bir standart kütüphane içerir. Bu genellikle birçok harici bağımlılığa olan ihtiyacı azaltır.
  • Basitlik: Go'nun sözdizimi kasıtlı olarak küçük ve temizdir, okunabilirlik ve sürdürülebilirlik için tasarlanmıştır. Basitliği korumak için klasik kalıtım, operatör aşırı yüklemesi ve (sürüm 1.18'e kadar) jenerik programlama gibi özellikleri kasıtlı olarak dışarıda bırakır.
  • Güçlü Araç Takımı: Go, kodu biçimlendirme (gofmt), bağımlılıkları yönetme (go mod), test etme (go test), derleme (go build) ve daha fazlası için mükemmel komut satırı araçlarıyla birlikte gelir ve geliştirme sürecini kolaylaştırır.

Dilin, Renée French tarafından tasarlanan ve Go topluluğunun bir sembolü haline gelen Gopher adında sevimli bir maskotu bile vardır. Resmi adı Go olsa da, “Golang” terimi orijinal web sitesi alan adı (golang.org) nedeniyle ortaya çıkmıştır ve özellikle çevrimiçi arama yaparken yaygın bir takma ad olarak kalmıştır.

Kurulum (Kısaca)

Herhangi bir Go kodu yazmadan önce Go derleyicisine ve araçlarına ihtiyacınız vardır. Resmi Go web sitesi olan go.dev adresini ziyaret edin ve işletim sisteminiz (Windows, macOS, Linux) için basit kurulum talimatlarını izleyin. Yükleyici, go gibi gerekli komutları ayarlar.

İlk Go Programınız: Merhaba, Gopher!

Geleneksel ilk programı oluşturalım. hello.go adında bir dosya oluşturun ve aşağıdaki kodu yazın veya yapıştırın:

package main

import "fmt"

// Bu, yürütmenin başladığı ana fonksiyondur.
func main() {
    // Println, konsola bir satır metin yazdırır.
    fmt.Println("Merhaba, Gopher!")
}

Bu basit Go kodu örneğini inceleyelim:

  1. package main: Her Go programı bir paket bildirimi ile başlar. main paketi özeldir; bu paketin çalıştırılabilir bir programa derlenmesi gerektiğini belirtir.
  2. import "fmt": Bu satır, Go'nun standart kütüphanesinin bir parçası olan fmt paketini içe aktarır. fmt paketi, konsola metin yazdırmak gibi biçimlendirilmiş girdi ve çıktı (G/Ç) için fonksiyonlar sağlar.
  3. func main() { ... }: Bu, main fonksiyonunu tanımlar. Çalıştırılabilir bir Go programının yürütülmesi her zaman main paketinin main fonksiyonunda başlar.
  4. fmt.Println("Merhaba, Gopher!"): Bu, içe aktarılan fmt paketinden Println fonksiyonunu çağırır. Println (Satır Yazdır), “Merhaba, Gopher!” metin dizesini konsola yazdırır ve ardından bir yeni satır karakteri ekler.

Bu programı çalıştırmak için terminalinizi veya komut istemcinizi açın, hello.go dosyasını kaydettiğiniz dizine gidin ve şu komutu yürütün:

go run hello.go

Konsolunuzda aşağıdaki çıktıyı görmelisiniz:

Merhaba, Gopher!

Tebrikler! İlk Go programınızı çalıştırdınız.

Go Programlama Dili Eğitimi: Temel Kavramlar

İlk programınız başarıyla çalıştığına göre, Go dilinin temel yapı taşlarını keşfedelim. Bu bölüm, başlangıç seviyesi bir Go eğitimi olarak hizmet vermektedir.

Değişkenler

Değişkenler, program yürütülmesi sırasında değişebilecek verileri saklamak için kullanılır. Go'da, değişkenleri kullanmadan önce bildirmeniz gerekir, bu da derleyicinin tip güvenliğini sağlamasına yardımcı olur.

  • var Kullanımı: var anahtar kelimesi, bir veya daha fazla değişken bildirmek için standart yoldur. Tipi, değişken adından sonra açıkça belirtebilirsiniz.

    package main
    
    import "fmt"
    
    func main() {
        var selamlama string = "Go'ya Hoş Geldiniz!" // Bir string değişkeni bildir
        var puan int = 100                 // Bir integer değişkeni bildir
        var pi float64 = 3.14159            // 64-bit kayan noktalı bir değişken bildir
        var aktifMi bool = true              // Bir boolean değişkeni bildir
    
        fmt.Println(selamlama)
        fmt.Println("Başlangıç Puanı:", puan)
        fmt.Println("Pi Yaklaşık:", pi)
        fmt.Println("Aktif Durumu:", aktifMi)
    }
    
  • Kısa Değişken Bildirimi :=: Fonksiyonlar içinde Go, değişkenleri aynı anda bildirmek ve başlatmak için kısa := sözdizimini sunar. Go, değişkenin tipini sağ tarafta atanan değerden otomatik olarak çıkarır.

    package main
    
    import "fmt"
    
    func main() {
        kullaniciAdi := "Gopher123" // Go, 'kullaniciAdi'nın bir string olduğunu çıkarır
        seviye := 5            // Go, 'seviye'nin bir int olduğunu çıkarır
        ilerleme := 0.75      // Go, 'ilerleme'nin bir float64 olduğunu çıkarır
    
        fmt.Println("Kullanıcı Adı:", kullaniciAdi)
        fmt.Println("Seviye:", seviye)
        fmt.Println("İlerleme:", ilerleme)
    }
    

    Önemli Not: := sözdizimi yalnızca fonksiyonlar içinde kullanılabilir. Paket seviyesinde (herhangi bir fonksiyonun dışında) bildirilen değişkenler için var anahtar kelimesini kullanmalısınız.

  • Sıfır Değerler: Eğer bir değişkeni var kullanarak açık bir başlangıç değeri vermeden bildirirseniz, Go ona otomatik olarak bir sıfır değer atar. Sıfır değer tipe bağlıdır:

    • Tüm sayısal tipler (int, float, vb.) için 0
    • Boolean tipleri (bool) için false
    • string tipleri için "" (boş dize)
    • İşaretçiler, arayüzler, map'ler, dilimler, kanallar ve başlatılmamış fonksiyon tipleri için nil.
    package main
    
    import "fmt"
    
    func main() {
        var sayac int
        var mesaj string
        var etkin bool
        var kullaniciPuani *int // İşaretçi tipi
        var gorev func() // Fonksiyon tipi
    
        fmt.Println("Sıfır Int:", sayac)       // Çıktı: Sıfır Int: 0
        fmt.Println("Sıfır String:", mesaj)  // Çıktı: Sıfır String:
        fmt.Println("Sıfır Bool:", etkin)   // Çıktı: Sıfır Bool: false
        fmt.Println("Sıfır İşaretçi:", kullaniciPuani) // Çıktı: Sıfır İşaretçi: <nil>
        fmt.Println("Sıfır Fonksiyon:", gorev)   // Çıktı: Sıfır Fonksiyon: <nil>
    }
    

Temel Veri Tipleri

Go, birkaç temel yerleşik veri tipi sağlar:

  • Tamsayılar (int, int8, int16, int32, int64, uint, uint8, vb.): Tam sayıları temsil eder. int ve uint platforma bağlıdır (genellikle 32 veya 64 bit). Gerektiğinde belirli boyutları kullanın (örn. ikili veri formatları veya performans optimizasyonu için). uint8, byte için bir takma addır.
  • Kayan Noktalı Sayılar (float32, float64): Ondalık noktalı sayıları temsil eder. float64 varsayılandır ve genellikle daha iyi hassasiyet için tercih edilir.
  • Booleanlar (bool): Doğruluk değerlerini temsil eder, ya true ya da false.
  • Dizeler (string): UTF-8 olarak kodlanmış karakter dizilerini temsil eder. Go'daki dizeler değişmezdir – bir kez oluşturulduktan sonra içerikleri doğrudan değiştirilemez. Dizeleri değiştiriyormuş gibi görünen işlemler aslında yenilerini oluşturur.

İşte temel tipleri kullanan bir Go örneği:

package main

import "fmt"

func main() {
	urun := "Laptop" // string
	miktar := 2     // int
	fiyat := 1250.75  // float64 (çıkarıldı)
	stoktaVar := true   // bool

	// Go, farklı sayısal tipler arasında açık tip dönüşümleri gerektirir.
	toplamMaliyet := float64(miktar) * fiyat // Çarpma için int 'miktar'ı float64'e dönüştür

	fmt.Println("Ürün:", urun)
	fmt.Println("Miktar:", miktar)
	fmt.Println("Birim Fiyat:", fiyat)
	fmt.Println("Stokta Var:", stoktaVar)
	fmt.Println("Toplam Maliyet:", toplamMaliyet)
}

Bu örnek, tip çıkarımı kullanarak değişken bildirimini ve farklı sayısal tiplerle aritmetik işlem yaparken açık tip dönüşümü ihtiyacını vurgulamaktadır.

Sabitler

Sabitler, değişkenlere benzer şekilde adları değerlere bağlar, ancak değerleri derleme zamanında sabittir ve program yürütülmesi sırasında değiştirilemez. const anahtar kelimesi kullanılarak bildirilirler.

package main

import "fmt"

const UygulamaSurumu = "1.0.2" // String sabiti
const MaksBaglanti = 1000 // Tamsayı sabiti
const Pi = 3.14159          // Kayan noktalı sabit

func main() {
	fmt.Println("Uygulama Sürümü:", UygulamaSurumu)
	fmt.Println("İzin Verilen Maksimum Bağlantı:", MaksBaglanti)
	fmt.Println("Pi değeri:", Pi)
}

Go ayrıca, artan tamsayı sabitlerinin tanımını basitleştiren özel iota anahtar kelimesini sağlar. Genellikle numaralandırılmış tipler (enum'lar) oluşturmak için kullanılır. iota, bir const bloğu içinde 0'dan başlar ve o bloktaki her bir sonraki sabit bildirimi için bir artar.

package main

import "fmt"

// int tabanlı özel LogSeviyesi tipi tanımla
type LogSeviyesi int

const (
	Debug LogSeviyesi = iota // 0
	Info                  // 1 (iota artar)
	Warning               // 2
	Error                 // 3
)

func main() {
	mevcutSeviye := Info
	fmt.Println("Mevcut Log Seviyesi:", mevcutSeviye) // Çıktı: Mevcut Log Seviyesi: 1
	fmt.Println("Hata Seviyesi:", Error)             // Çıktı: Hata Seviyesi: 3
}

Kontrol Akışı

Kontrol akışı ifadeleri, kod ifadelerinin hangi sırada yürütüleceğini belirler.

  • if / else if / else: Boolean ifadelerine dayalı olarak kod bloklarını koşullu olarak yürütür. Go'da koşullar etrafında parantez () kullanılmaz, ancak küme parantezleri {} her zaman gereklidir, tek ifadeli bloklar için bile.

    package main
    
    import "fmt"
    
    func main() {
        sicaklik := 25
    
        if sicaklik > 30 {
            fmt.Println("Hava oldukça sıcak.")
        } else if sicaklik < 10 {
            fmt.Println("Hava oldukça soğuk.")
        } else {
            fmt.Println("Sıcaklık ılıman.") // Bu yazdırılacak
        }
    
        // Koşuldan önce kısa bir ifade gelebilir; orada bildirilen değişkenler
        // if/else bloğuyla sınırlıdır.
        if limit := 100; sicaklik < limit {
            fmt.Printf("Sıcaklık %d, limit %d'nin altında.\n", sicaklik, limit)
        } else {
             fmt.Printf("Sıcaklık %d, limit %d'nin altında DEĞİL.\n", sicaklik, limit)
        }
    }
    
  • for: Go'nun yalnızca bir döngü yapısı vardır: çok yönlü for döngüsü. Diğer dillerden aşina olunan çeşitli şekillerde kullanılabilir:

    • Klasik for döngüsü (başlatma; koşul; son işlem):
      for i := 0; i < 5; i++ {
          fmt.Println("İterasyon:", i)
      }
      
    • Yalnızca koşullu döngü (while döngüsü gibi davranır):
      toplam := 1
      for toplam < 100 { // toplam 100'den küçük olduğu sürece döngü
          toplam += toplam
      }
      fmt.Println("Son toplam:", toplam) // Çıktı: Son toplam: 128
      
    • Sonsuz döngü (çıkmak için break veya return kullanın):
      sayac := 0
      for {
          fmt.Println("Döngüde...")
          sayac++
          if sayac > 3 {
              break // Döngüden çık
          }
      }
      
    • for...range: Dilimler, diziler, map'ler, dizeler ve kanallar gibi veri yapılarındaki öğeler üzerinde yineler. Her öğe için dizin/anahtar ve değeri sağlar.
      renkler := []string{"Kırmızı", "Yeşil", "Mavi"}
      // Hem dizini hem de değeri al
      for indeks, renk := range renkler {
          fmt.Printf("İndeks: %d, Renk: %s\n", indeks, renk)
      }
      
      // Yalnızca değere ihtiyacınız varsa, dizini yok saymak için boş tanımlayıcı _ kullanın
      fmt.Println("Renkler:")
      for _, renk := range renkler {
           fmt.Println("- ", renk)
      }
      
      // Bir dizedeki karakterler (runelar) üzerinde yinele
      for i, r := range "Go!" {
           fmt.Printf("İndeks %d, Rune %c\n", i, r)
      }
      
  • switch: Uzun if-else if zincirlerine daha temiz bir alternatif sağlayan çok yönlü bir koşullu ifade. Go'nun switch'i birçok C benzeri dilden daha esnektir:

    • Durumlar varsayılan olarak bir sonrakine geçmez (break gerekmez).
    • Durumlar birden fazla değer içerebilir.
    • Bir switch, bir ifade olmadan kullanılabilir (true değerini durum ifadeleriyle karşılaştırır).
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        gun := time.Now().Weekday()
        fmt.Println("Bugün:", gun) // Örnek: Bugün: Tuesday
    
        switch gun {
        case time.Saturday, time.Sunday: // Bir durum için birden fazla değer
            fmt.Println("Hafta sonu!")
        case time.Monday:
             fmt.Println("Çalışma haftasının başlangıcı.")
        default: // İsteğe bağlı varsayılan durum
            fmt.Println("Hafta içi.")
        }
    
        // İfadesiz switch, temiz bir if/else if zinciri gibi davranır
        saat := time.Now().Hour()
        switch { // Örtük olarak 'true' üzerinde geçiş yapılır
        case saat < 12:
            fmt.Println("Günaydın!")
        case saat < 17:
            fmt.Println("Tünaydın!")
        default:
            fmt.Println("İyi akşamlar!")
        }
    }
    

Örneklerle Golang Öğrenin: Veri Yapıları

Go, birkaç temel veri yapısı için yerleşik destek sağlar.

Diziler

Go'daki diziler, bildirim anında belirlenen sabit bir boyuta sahiptir. Boyut, dizinin tipinin bir parçasıdır ([3]int, [4]int'den farklı bir tiptir).

package main

import "fmt"

func main() {
	// 3 tamsayıdan oluşan bir dizi bildir. Sıfır değerleriyle (0'lar) başlatılır.
	var sayilar [3]int
	sayilar[0] = 10
	sayilar[1] = 20
	// sayilar[2] 0 olarak kalır (sıfır değer)

	fmt.Println("Sayılar:", sayilar)      // Çıktı: Sayılar: [10 20 0]
	fmt.Println("Uzunluk:", len(sayilar)) // Çıktı: Uzunluk: 3

	// Satır içinde bir dizi bildir ve başlat
	asallar := [5]int{2, 3, 5, 7, 11}
	fmt.Println("Asallar:", asallar)       // Çıktı: Asallar: [2 3 5 7 11]

	// Derleyicinin elemanları ... kullanarak saymasına izin ver
	sesliler := [...]string{"a", "e", "i", "o", "u"}
	fmt.Println("Sesliler:", sesliler, "Uzunluk:", len(sesliler)) // Çıktı: Sesliler: [a e i o u] Uzunluk: 5
}

Dizilerin kullanım alanları olsa da (örneğin, boyut gerçekten sabit ve biliniyorsa), esneklikleri nedeniyle Go'da dilimler çok daha yaygın olarak kullanılır.

Dilimler

Dilimler, Go'da diziler için kullanılan temel veri yapısıdır. Dizilere göre daha güçlü, esnek ve kullanışlı bir arayüz sağlarlar. Dilimler dinamik boyutludur ve temel alınan dizilere değiştirilebilir görünümler sunar.

package main

import "fmt"

func main() {
	// make(tip, uzunluk, kapasite) kullanarak bir string dilimi oluştur
	// Kapasite isteğe bağlıdır; belirtilmezse, uzunluğa varsayılan olarak ayarlanır.
	// Uzunluk: dilimin şu anda içerdiği eleman sayısı.
	// Kapasite: temel alınan dizideki eleman sayısı (dilimin ilk elemanından başlayarak).
	isimler := make([]string, 2, 5) // Uzunluk 2, Kapasite 5
	isimler[0] = "Alice"
	isimler[1] = "Bob"

	fmt.Println("Başlangıç İsimleri:", isimler, "Len:", len(isimler), "Cap:", cap(isimler)) // Çıktı: Başlangıç İsimleri: [Alice Bob] Len: 2 Cap: 5

	// Append, sona eleman ekler. Uzunluk kapasiteyi aşarsa,
	// yeni, daha büyük bir temel dizi ayrılır ve dilim ona işaret eder.
	isimler = append(isimler, "Charlie")
	isimler = append(isimler, "David", "Eve") // Birden fazla eleman eklenebilir

	fmt.Println("Eklenen İsimler:", isimler, "Len:", len(isimler), "Cap:", cap(isimler)) // Çıktı: Eklenen İsimler: [Alice Bob Charlie David Eve] Len: 5 Cap: 5 (veya yeniden tahsis edilirse muhtemelen daha büyük)

	// Dilim değişmezi (bir dilim ve temel bir dizi oluşturur)
	puanlar := []int{95, 88, 72, 100}
	fmt.Println("Puanlar:", puanlar) // Çıktı: Puanlar: [95 88 72 100]

	// Bir dilimi dilimlemek: *aynı* temel diziye başvuran yeni bir dilim başlığı oluşturur.
	// dilim[düşük:yüksek] - düşük indeksteki elemanı içerir, yüksek indeksteki elemanı hariç tutar.
	enYuksekPuanlar := puanlar[1:3] // İndeks 1 ve 2'deki elemanlar (değer: 88, 72)
	fmt.Println("En Yüksek Puanlar:", enYuksekPuanlar) // Çıktı: En Yüksek Puanlar: [88 72]

	// Alt dilimi değiştirmek orijinal dilimi (ve temel diziyi) etkiler
	enYuksekPuanlar[0] = 90
	fmt.Println("Değiştirilmiş Puanlar:", puanlar) // Çıktı: Değiştirilmiş Puanlar: [95 90 72 100]

    // Düşük sınırı atlamak varsayılan olarak 0'dır, yüksek sınırı atlamak varsayılan olarak uzunluktur
    ilkIki := puanlar[:2]
    sonIki := puanlar[2:]
    fmt.Println("İlk İki:", ilkIki) // Çıktı: İlk İki: [95 90]
    fmt.Println("Son İki:", sonIki)  // Çıktı: Son İki: [72 100]
}

Temel dilim işlemleri arasında len() (mevcut uzunluk), cap() (mevcut kapasite), append() (eleman ekleme) ve [düşük:yüksek] sözdizimini kullanarak dilimleme bulunur.

Map'ler

Map'ler (Haritalar), Go'nun yerleşik hash tabloları veya sözlük uygulamasıdır. Sırasız anahtar-değer çiftleri koleksiyonlarını saklarlar; burada tüm anahtarlar aynı türde ve tüm değerler aynı türde olmalıdır.

package main

import "fmt"

func main() {
	// make kullanarak string anahtarları ve int değerleri olan boş bir map oluştur
	yaslar := make(map[string]int)

	// Anahtar-değer çiftlerini ayarla
	yaslar["Alice"] = 30
	yaslar["Bob"] = 25
	yaslar["Charlie"] = 35
	fmt.Println("Yaşlar map'i:", yaslar) // Çıktı: Yaşlar map'i: map[Alice:30 Bob:25 Charlie:35] (sıra garanti edilmez)

	// Anahtarı kullanarak bir değer al
	aliceYasi := yaslar["Alice"]
	fmt.Println("Alice'in Yaşı:", aliceYasi) // Çıktı: Alice'in Yaşı: 30

	// Var olmayan bir anahtar için değer almak, değer türü için sıfır değerini döndürür (int için 0)
	davidYasi := yaslar["David"]
	fmt.Println("David'in Yaşı:", davidYasi) // Çıktı: David'in Yaşı: 0

	// Bir anahtar-değer çiftini sil
	delete(yaslar, "Bob")
	fmt.Println("Bob Silindikten Sonra:", yaslar) // Çıktı: Bob Silindikten Sonra: map[Alice:30 Charlie:35]

	// İki değerli atama formunu kullanarak bir anahtarın var olup olmadığını kontrol et
	// Bir map anahtarına erişirken, isteğe bağlı olarak ikinci bir boolean değeri alabilirsiniz:
	// 1. Değer (veya anahtar yoksa sıfır değeri)
	// 2. Bir boolean: anahtar mevcutsa true, aksi takdirde false
	val, mevcut := yaslar["Bob"] // Değere ihtiyaç yoksa boş tanımlayıcı _ kullanın (örn. _, mevcut := ...)
	fmt.Printf("Bob mevcut mu? %t, Değer: %d\n", mevcut, val) // Çıktı: Bob mevcut mu? false, Değer: 0

	charlieYasi, charlieMevcut := yaslar["Charlie"]
	fmt.Printf("Charlie mevcut mu? %t, Yaş: %d\n", charlieMevcut, charlieYasi) // Çıktı: Charlie mevcut mu? true, Yaş: 35

	// Bir map bildirmek ve başlatmak için map değişmezi
	baskentler := map[string]string{
		"France": "Paris",
		"Japan":  "Tokyo",
		"USA":    "Washington D.C.",
	}
	fmt.Println("Başkentler:", baskentler)
}

Fonksiyonlar

Fonksiyonlar, kodu yeniden kullanılabilir birimler halinde organize etmek için temel yapı taşlarıdır. func anahtar kelimesi kullanılarak bildirilirler.

package main

import (
	"fmt"
	"errors" // Hata değerleri oluşturmak için standart kütüphane paketi
)

// İki int parametresi alan ve bunların int toplamını döndüren basit bir fonksiyon.
// Parametre tipleri adı takip eder: func fonksiyonAdi(param1 tip1, param2 tip2) donusTipi { ... }
func topla(x int, y int) int {
	return x + y
}

// Ardışık parametreler aynı tipe sahipse, tipi
// sonuncusu hariç hepsinden çıkarabilirsiniz.
func carp(x, y int) int {
    return x * y
}

// Go fonksiyonları birden fazla değer döndürebilir. Bu, bir sonucu
// ve bir hata durumunu aynı anda döndürmek için deyimsel bir yoldur.
func bol(pay float64, payda float64) (float64, error) {
	if payda == 0 {
		// Payda sıfırsa yeni bir hata değeri oluştur ve döndür
		return 0, errors.New("sıfıra bölmeye izin verilmez")
	}
	// Başarılı olursa hesaplanan sonucu ve hata için 'nil' döndür
	// 'nil', hata tipleri (ve işaretçiler, dilimler, map'ler gibi diğerleri) için sıfır değeridir.
	return pay / payda, nil
}

func main() {
	toplam := topla(15, 7)
	fmt.Println("Toplam:", toplam) // Çıktı: Toplam: 22

	carpim := carp(6, 7)
	fmt.Println("Çarpım:", carpim) // Çıktı: Çarpım: 42

	// Birden fazla değer döndüren fonksiyonu çağır
	sonuc, err := bol(10.0, 2.0)
	// Hata değerini hemen kontrol et
	if err != nil {
		fmt.Println("Hata:", err)
	} else {
		fmt.Println("Bölme Sonucu:", sonuc) // Çıktı: Bölme Sonucu: 5
	}

	// Geçersiz girdiyle tekrar çağır
	sonuc2, err2 := bol(5.0, 0.0)
	if err2 != nil {
		fmt.Println("Hata:", err2) // Çıktı: Hata: sıfıra bölmeye izin verilmez
	} else {
		fmt.Println("Bölme Sonucu 2:", sonuc2)
	}
}

Go fonksiyonlarının birden fazla değer döndürme yeteneği, açık hata yönetimi mekanizması için çok önemlidir.

Paketler

Go kodu paketler halinde organize edilir. Bir paket, birlikte derlenen tek bir dizinde bulunan kaynak dosyaları (.go dosyaları) koleksiyonudur. Paketler kodun yeniden kullanımını ve modülerliği teşvik eder.

  • Paket Bildirimi: Her Go kaynak dosyası bir package paketAdi bildirimi ile başlamalıdır. Aynı dizindeki dosyalar aynı pakete ait olmalıdır. main paketi özeldir, çalıştırılabilir bir programı belirtir.
  • Paketleri İçe Aktarma: Diğer paketlerde tanımlanan koda erişmek için import anahtar kelimesini kullanın. Standart kütüphane paketleri kısa adlarıyla içe aktarılır (örn. "fmt", "math", "os"). Harici paketler genellikle kaynak depo URL'lerine dayalı bir yol kullanır (örn. "github.com/gin-gonic/gin").
    import (
        "fmt"        // Standart kütüphane
        "math/rand"  // math'ın alt paketi
        "os"
        // disPaketim "github.com/kullanici/haricipaket" // İçe aktarmalara takma ad verilebilir
    )
    
  • Dışa Aktarılan Adlar: Bir paket içindeki tanımlayıcılar (değişkenler, sabitler, tipler, fonksiyonlar, metotlar), adları büyük harfle başlıyorsa dışa aktarılır (diğer paketlerden görülebilir ve kullanılabilir). Küçük harfle başlayan tanımlayıcılar dışa aktarılmaz (tanımlandıkları pakete özeldir).
  • Go Modülleri ile Bağımlılık Yönetimi: Modern Go, proje bağımlılıklarını yönetmek için Modülleri kullanır. Bir modül, projenin kök dizinindeki bir go.mod dosyasıyla tanımlanır. Temel komutlar şunları içerir:
    • go mod init <modül_yolu>: Yeni bir modül başlatır (go.mod oluşturur).
    • go get <paket_yolu>: Bir bağımlılığı ekler veya günceller.
    • go mod tidy: Kullanılmayan bağımlılıkları kaldırır ve kod içe aktarmalarına dayalı olarak eksik olanları ekler.

Eşzamanlılığa Bir Bakış: Goroutine'ler ve Kanallar

Eşzamanlılık, aynı anda çalışıyormuş gibi görünen birden fazla görevi yönetmeyi içerir. Go, İletişim Kuran Sıralı Süreçler'den (CSP) esinlenerek eşzamanlılık için güçlü ancak basit yerleşik özelliklere sahiptir.

  • Goroutine'ler: Bir goroutine, Go çalışma zamanı tarafından başlatılan ve yönetilen bağımsız olarak yürütülen bir fonksiyondur. Bunu son derece hafif bir iş parçacığı olarak düşünün. Bir fonksiyon veya metot çağrısının başına go anahtar kelimesini ekleyerek basitçe bir goroutine başlatırsınız.

  • Kanallar: Kanallar, goroutine'ler arasında değer gönderip almanızı sağlayan, iletişim ve senkronizasyonu mümkün kılan tipli iletkenlerdir.

    • Bir kanal oluşturma: ch := make(chan Tip) (örn. make(chan string))
    • Bir değer gönderme: ch <- deger
    • Bir değer alma: degisken := <-ch (bu, bir değer gönderilene kadar engellenir)

İşte goroutine'leri ve kanalları gösteren çok temel bir Go örneği:

package main

import (
	"fmt"
	"time"
)

// Bu fonksiyon bir goroutine olarak çalışacak.
// Bir mesaj ve mesajı geri göndermek için bir kanal alır.
func mesajGoster(msg string, messages chan string) {
	fmt.Println("Goroutine çalışıyor...")
	time.Sleep(1 * time.Second) // Biraz iş simüle et
	messages <- msg             // Mesajı kanala gönder
	fmt.Println("Goroutine bitti.")
}

func main() {
	// String değerleri taşıyan bir kanal oluştur.
	// Bu, arabelleksiz bir kanaldır, yani gönderme/alma işlemleri
	// diğer taraf hazır olana kadar engellenir.
	mesajKanali := make(chan string)

	// mesajGoster fonksiyonunu bir goroutine olarak başlat
	// 'go' anahtar kelimesi bu çağrıyı engellemeyen yapar; main hemen devam eder.
	go mesajGoster("Ping!", mesajKanali)

	fmt.Println("Ana fonksiyon mesaj bekliyor...")

	// Kanaldan mesajı al.
	// Bu işlem, goroutine tarafından mesajKanali'na bir mesaj gönderilene
	// kadar ana fonksiyonu ENGELLER.
	alinanMsg := <-mesajKanali

	fmt.Println("Ana fonksiyon aldı:", alinanMsg) // Çıktı (~1 saniye sonra): Ana fonksiyon aldı: Ping!

    // Goroutine'in son yazdırma ifadesinin main çıkmadan önce görünmesine izin ver
    time.Sleep(50 * time.Millisecond)
}

Bu basit örnek, eşzamanlı bir görevi başlatmayı ve sonucunu bir kanal aracılığıyla güvenli bir şekilde almayı göstermektedir. Go'nun eşzamanlılık modeli, arabellekli kanalları, birden fazla kanalı işlemek için güçlü select ifadesini ve sync paketindeki senkronizasyon ilkellerini içeren derin bir konudur.

Go'da Hata Yönetimi

Go, istisnaları kullanan dillere kıyasla hata yönetimine farklı bir yaklaşım benimser. Hatalar normal değerler olarak ele alınır. Potansiyel olarak başarısız olabilecek fonksiyonlar tipik olarak son dönüş değeri olarak bir error arayüz tipi döndürür.

  • error arayüzünün tek bir metodu vardır: Error() string.
  • nil bir hata değeri başarıyı gösterir.
  • nil olmayan bir hata değeri başarısızlığı gösterir ve değerin kendisi genellikle hatayla ilgili ayrıntıları içerir.
  • Standart model, fonksiyonu çağırdıktan hemen sonra döndürülen hatayı kontrol etmektir.
package main

import (
	"fmt"
	"os"
)

func main() {
	// Muhtemelen var olmayan bir dosyayı açmaya çalış
	dosya, err := os.Open("kesinlikle_var_olmayan_bir_dosya.txt")

	// Deyimsel hata kontrolü: err'nin nil olup olmadığını kontrol et
	if err != nil {
		fmt.Println("KRİTİK: Dosya açılırken hata:", err)
		// Hatayı uygun şekilde ele al. Burada sadece çıkıyoruz.
		// Gerçek uygulamalarda, hatayı günlüğe kaydedebilir,
		// mevcut fonksiyondan döndürebilir veya bir geri dönüş deneyebilirsiniz.
		return // Ana fonksiyondan çık
	}

	// Eğer err nil ise, fonksiyon başarılı olmuştur.
	// Artık 'dosya' değişkenini güvenle kullanabiliriz.
	fmt.Println("Dosya başarıyla açıldı!") // Bu hata senaryosunda yazdırılmayacak

	// Dosyalar gibi kaynakları kapatmak çok önemlidir.
	// 'defer', bir fonksiyon çağrısını (dosya.Close()) çevreleyen
	// fonksiyon (main) dönmeden hemen önce çalışacak şekilde zamanlar.
	defer dosya.Close()

	// ... dosyadan okuma veya dosyaya yazma işlemlerine devam et ...
	fmt.Println("Dosya üzerinde işlemler gerçekleştiriliyor...")
}

Bu açık if err != nil kontrolü, kontrol akışını çok net hale getirir ve geliştiricileri potansiyel başarısızlıkları aktif olarak düşünmeye ve ele almaya teşvik eder. defer ifadesi, kaynakların güvenilir bir şekilde temizlenmesini sağlamak için genellikle hata kontrolleriyle birlikte kullanılır.

Temel Go Araçları

Go'nun önemli bir gücü, standart dağıtımla birlikte gelen mükemmel, uyumlu araç takımıdır:

  • go run <dosyaadi.go>: Tek bir Go kaynak dosyasını veya bir main paketini doğrudan derler ve çalıştırır. Hızlı testler için kullanışlıdır.
  • go build: Go paketlerini ve bağımlılıklarını derler. Varsayılan olarak, paket main ise çalıştırılabilir bir dosya oluşturur.
  • gofmt: Go kaynak kodunu resmi Go stil yönergelerine göre otomatik olarak biçimlendirir. Projeler ve geliştiriciler arasında tutarlılık sağlar. Mevcut dizindeki ve alt dizinlerdeki tüm Go dosyalarını biçimlendirmek için gofmt -w . kullanın.
  • go test: Birim testlerini ve kıyaslamaları çalıştırır. Testler _test.go dosyalarında bulunur.
  • go mod: Bağımlılıkları yönetmek için Go modülleri aracı (örn. go mod init, go mod tidy, go mod download).
  • go get <paket_yolu>: Mevcut modülünüze yeni bağımlılıklar ekler veya mevcut olanları günceller.
  • go vet: Go kaynak kodunu şüpheli yapılar ve derleyicinin yakalayamayabileceği potansiyel hatalar açısından kontrol eden statik bir analiz aracıdır.
  • go doc <paket> [sembol]: Paketler veya belirli semboller için belgeleri görüntüler.

Bu entegre araç takımı, derleme, test etme, biçimlendirme ve bağımlılık yönetimi gibi yaygın geliştirme görevlerini önemli ölçüde basitleştirir.

Sonuç

Go, modern yazılım geliştirme için çekici bir teklif sunar: basitliği, performansı ve güçlü özellikleri, özellikle eşzamanlı sistemler, ağ hizmetleri ve büyük ölçekli uygulamalar oluşturmak için dengeleyen bir dil. Temiz sözdizimi, güçlü statik tipleme, çöp toplama yoluyla otomatik bellek yönetimi, yerleşik eşzamanlılık ilkelleri, kapsamlı standart kütüphane ve mükemmel araç takımı; daha hızlı geliştirme döngülerine, daha kolay bakıma ve daha güvenilir yazılımlara katkıda bulunur. Bu, onu yalnızca yeni yeşil alan projeleri için değil, aynı zamanda performans, eşzamanlılık ve sürdürülebilirliğin temel hedefler olduğu mevcut sistemleri modernize etmek veya kod taşımak için de güçlü bir seçenek haline getirir.

İlgili makaleler