---
title: 'Gömülü Kaynaklar'
source: 'https://academia.sh/tr/kurslar/go-uygulamalar/gomulu-kaynaklar'
course: 'Standart Kütüphane ve Ağ Uygulamaları'
language: tr
updated: '2026-08-23T16:55:13+00:00'
license: 'CC BY-SA 4.0'
---

# Gömülü Kaynaklar

Bir kaynak dosyanın go:embed yönergesiyle üç ayrı Go tipine (dizgi, bayt dilimi, dosya sistemi) aynı içerikle bağlandığı ve bu içeriğin diskte duran dosyayla birebir eşleştiği ölçülür. Sınırlayıcı ölçüm, bir kez derlenmiş ikilinin kaynak dosya diskten silindikten sonra bile aynı içeriği ürettiğini koşturarak gösteriyor: bağ derleme zamanında kuruluyor, koşum zamanında dosyaya hiç ihtiyaç kalmıyor.

Önceki ders üç kaynağı (dosya, arabellek, dizgi) aynı okuyucu arayüzünden geçirdi ve hepsinin
aynı sonucu — ham bir bayt dilimi — verdiğini gösterdi. O üç kaynağın ortak bir özelliği vardı:
programın kendisi hâlâ çalışırken erişilebilir kalıyorlardı — dosya diskte duruyordu, arabellek
bellekte. Bu ders farklı bir soru soruyor: bir programın ihtiyaç duyduğu bir veri, koşum
sırasında hiç erişilebilir olmasa bile programın elinde bulunabilir mi? Yanıt evet, ve yöntem
bir okuma işlemi değil, bir **bağlama** işlemi: kaynak dosyanın içeriğini, derleme sırasında
doğrudan ikili dosyanın içine yerleştirmek.

**Terim notu:** bu derste geçen "gömme" sözcüğü, Bileşik Tipler konusunda ölçülen **yapı
gömme**yle (bir tipin başka bir tipin alanları arasına adsız olarak yerleştirilmesi) aynı
kelimeyi kullanıyor ama ayrı bir düzenek. Yapı gömme kaynak kodun **içinde**, iki Go tipi
arasında kurulan bir ilişkiydi; bu derste ölçülen **gömülü kaynak** ise bir dosyanın içeriğini
kaynak kodun **dışından**, derleyicinin kendisi aracılığıyla bir değişkene taşıyor. İkisinin
ortak yanı yalnızca isim; biri iki Go tipini birbirine, öteki bir dosyayı bir Go değerine
bağlıyor.

**Belirlenimcilik notu:** bu derste hiçbir dosya yolu basılmıyor; ölçülen tek şey içeriğin
uzunluğu, tipi ve iki temsilin birbirine eşit olup olmadığı.

Bu düzeneğin tipik kullanım yerleri, koşum sırasında değişmesi beklenmeyen ama programın
kendisiyle birlikte taşınması gereken veriler: bir yapılandırma dosyasının varsayılan
değerleri, bir komut satırı aracının yardım metni, bir web sunucusunun statik varlıkları.
Hepsinin ortak noktası, içeriğin **programın bir parçası** olması — ayrı bir kurulum adımıyla
yanına kopyalanması gereken bir veri değil.

## Kaynak Dosya

Gömme yönergesi (`//go:embed`) bir `var` bildiriminin hemen üstüne yazılan özel bir yorum;
derleyici bunu okuyup, belirtilen dosyanın içeriğini o değişkene derleme sırasında yerleştiriyor.
Yönergenin çalışabilmesi için önce gömülecek dosyanın var olması gerekiyor — aşağıdaki kabuk
bloğu, dersin geri kalanında gömülecek `tarif.txt` dosyasını oluşturuyor ve içeriğini
doğrulamak için geri okuyor.

```bash
# tarif-dosyasi.sh — go:embed ile gomulecek kaynak dosyayi olusturur
cat > tarif.txt <<'EOF'
kaynak paketin icine kod ile birlikte tasinir
EOF
cat tarif.txt
```

```
kaynak paketin icine kod ile birlikte tasinir
```

## Aynı Dosya, Üç Tip

**SS4.** — Aynı `tarif.txt` dosyası üç ayrı değişkene gömülüyor: bir dizgiye, bir bayt
dilimine, bir de dosya sistemi türüne (`embed.FS`). Amaç, yönergenin hedef tipe göre farklı
davrandığını ve üçünün de aynı içeriği taşıdığını göstermek.

```go
// gomulu.go — ayni dosya string, []byte ve embed.FS olarak gomuluyor, icerikleri karsilastiriliyor
package main

import (
	"embed"
	"fmt"
	"os"
)

//go:embed tarif.txt
var tarifMetni string

//go:embed tarif.txt
var tarifBaytlari []byte

//go:embed tarif.txt
var tarifSistemi embed.FS

func main() {
	sistemdenOkunan, _ := tarifSistemi.ReadFile("tarif.txt")
	diskten, hataDosya := os.ReadFile("tarif.txt")

	fmt.Printf("tarifMetni tipi=%T uzunluk=%d\n", tarifMetni, len(tarifMetni))
	fmt.Printf("tarifBaytlari tipi=%T uzunluk=%d\n", tarifBaytlari, len(tarifBaytlari))
	fmt.Println("metin == bayt donusumu:", tarifMetni == string(tarifBaytlari))
	fmt.Println("metin == embed.FS okumasi:", tarifMetni == string(sistemdenOkunan))
	fmt.Println("metin == dosya sisteminden okunan:", tarifMetni == string(diskten), hataDosya == nil)
}
```

```
tarifMetni tipi=string uzunluk=46
tarifBaytlari tipi=[]uint8 uzunluk=46
metin == bayt donusumu: true
metin == embed.FS okumasi: true
metin == dosya sisteminden okunan: true true
```

Üç `//go:embed` yönergesi birbirinden bağımsız; her biri aynı `tarif.txt`yi okuyor ama hedef
değişkenin tipine göre farklı bir temsil üretiyor. `tarifMetni` bir dizgi, `tarifBaytlari` bir
bayt dilimi, `tarifSistemi` ise `Open` ve `ReadFile` gibi yöntemleri olan küçük bir dosya
sistemi soyutlaması. Üçü de aynı kaynaktan geldiği için içerikleri birebir eşleşiyor — bayt
dönüşümüyle karşılaştırıldığında da, `embed.FS` üzerinden okunduğunda da, dosyanın kendisinden
`os.ReadFile` ile canlı okunduğunda da aynı 46 bayt geliyor. Bu son karşılaştırma önceki
dersle bir köprü kuruyor: dosya hâlâ diskteyken, gömülen içerik ile diskteki içerik ayırt
edilemiyor — sanki dosya iki kez okunmuş gibi, ama biri derleme sırasında, biri koşum
sırasında gerçekleşiyor.

Aynı dosyanın üç ayrı temsile bölünmesi rastgele değil; her temsil farklı bir kullanım
biçimine uyuyor. Metni doğrudan bir dizgi olarak kullanacak kod (bir sabit metin, bir şablon
kaynağı) `tarifMetni`yi tercih ediyor. İçeriği başka bir ikili biçime (bir sıkıştırma
algoritmasına, bir ağ gövdesine) aktaracak kod `tarifBaytlari`yı tercih ediyor, çünkü `[]byte`
o tür arayüzlerin beklediği tip. Birden fazla dosyayı ya da bir dizin yapısını taşımak
gereken kod ise `embed.FS`e ihtiyaç duyuyor — bu ihtiyaç bir sonraki bölümde ölçülüyor.

## Sınırlayıcı Ölçüm: Kaynak Dosya Koşumda Hiç Aranmaz

Buraya kadarki ölçüm `tarif.txt` diskteyken çalıştı, bu yüzden "gömülü içerik dosyayla aynı"
iddiası ile "program dosyaya bağımlı" iddiası birbirinden ayrılamıyordu. Aşağıdaki adım bu
ikisini ayırıyor: aynı kaynak önce **derlenip** bağımsız bir ikili hâline getiriliyor, sonra
kaynak dosya diskten **siliniyor**, sonra da yalnızca o ikili çalıştırılıyor.

**SS5.** — Derleme adımı `go build` ile ayrı bir ikili üretiyor; `go run` kullanılmıyor,
çünkü `go run` her çağrıda yeniden derliyor ve kaynak dosya o anda hâlâ diskte olurdu. Burada
amaç, derleme ile çalıştırmayı **birbirinden ayırmak**.

```bash
# derle-ve-sil.sh — once derlenir, sonra kaynak dosya silinir, sonra yalniz ikili calistirilir
go build -o govde_programi .
rm tarif.txt
./govde_programi
```

```
tarifMetni tipi=string uzunluk=46
tarifBaytlari tipi=[]uint8 uzunluk=46
metin == bayt donusumu: true
metin == embed.FS okumasi: true
metin == dosya sisteminden okunan: false false
```

Çıktının ilk dört satırı bir önceki koşumla birebir aynı: `tarifMetni`, `tarifBaytlari` ve
`tarifSistemi` hâlâ 46 baytlık içeriği doğru biçimde veriyor — `tarif.txt` artık diskte
olmamasına rağmen. Değişen tek satır sonuncusu: `os.ReadFile("tarif.txt")` artık dosyayı
bulamıyor, bu yüzden hem karşılaştırma hem hata denetimi `false`a dönüyor. Bu fark, gömme
yönergesi ile canlı dosya okuması arasındaki gerçek ayrımı gösteriyor — ilk üç değişken
derleme sırasında içeriği **kendi içine aldı** ve o andan sonra kaynağı bir daha hiç
okumadı; dördüncü satır ise her çalıştırmada dosyayı **yeniden** arıyor.

Bu, konunun ölçü ekseninde şöyle okunuyor: kaynak dosyadan gömülen değişkene giden bağ,
programın koşum ânında **kurulmuyor** — derleme ânında zaten kurulmuş durumda. Bir dosyayı
okumaktan (önceki dersin konusu) farkı da tam burada: okuma her çağrıda kaynağa yeniden
gidiyor, gömme ise kaynağa yalnızca **bir kez**, derleme sırasında gidiyor ve sonucu ikilinin
içine taşıyor. Bu, bir kısıt olduğu kadar bir güvence de: dağıtılan ikili, yanına ayrı bir
veri dosyası taşımak zorunda kalmıyor, ve o dosyanın koşum ânında yanlışlıkla değiştirilmesi
ya da silinmesi programın davranışını hiç etkilemiyor.

Bunun bedeli de var, ve bedel tam olarak güvencenin ters yüzü: içerik güncellendiğinde, ikili
de yeniden derlenmek zorunda. Önceki dersteki dosya okuma, koşum sırasında değişen bir kaynağı
hemen görebiliyordu — dosyayı her seferinde yeniden okumak yeterliydi. Gömülü bir kaynak için
bu yol kapalı; içeriği güncellemenin tek yolu kaynak dosyayı değiştirip programı **yeniden
derlemek**. Hangi yaklaşımın doğru olduğu içeriğin kendi doğasına bağlı: sık değişen bir veri
için okuma, hiç değişmemesi gereken bir veri için gömme daha uygun bir seçim.

## Birden Çok Dosya: Örüntü ile Gömme

Yönergenin hedefi tek bir dosya adı değil, bir **örüntü** de olabiliyor. `string` ve `[]byte`
yalnızca tek bir dosyanın içeriğini taşıyabildiğinden, birden çok dosyayı gömmenin tek yolu
`embed.FS`; sonuç, ikilinin içine gömülmüş küçük bir dizin ağacı oluyor.

**SS6.** — İki dosyalık küçük bir dizin (`kaynaklar/`) örüntüyle (`kaynaklar/*.txt`) tek bir
`embed.FS` değişkenine gömülüyor; ölçüm, gömülen dosya sayısını ve her birinin uzunluğunu
okuyor.

```bash
# kaynaklar-dizini.sh — orunu ile gomulecek iki dosyalik bir dizin olusturur
mkdir kaynaklar
printf 'birinci dosyanin icerigi\n' > kaynaklar/birinci.txt
printf 'ikinci dosyanin icerigi, biraz daha uzun\n' > kaynaklar/ikinci.txt
ls kaynaklar
```

```
birinci.txt
ikinci.txt
```

```go
// dizin.go — orunu ile gomulen bir dizinin icerigi listeleniyor ve okunuyor
package main

import (
	"embed"
	"fmt"
	"sort"
)

//go:embed kaynaklar/*.txt
var kaynakDizini embed.FS

func main() {
	girdiler, _ := kaynakDizini.ReadDir("kaynaklar")
	adlar := make([]string, 0, len(girdiler))
	for _, g := range girdiler {
		adlar = append(adlar, g.Name())
	}
	sort.Strings(adlar)
	fmt.Println("gomulu dosya sayisi:", len(adlar))
	for _, ad := range adlar {
		icerik, _ := kaynakDizini.ReadFile("kaynaklar/" + ad)
		fmt.Printf("%s uzunluk=%d\n", ad, len(icerik))
	}
}
```

```
gomulu dosya sayisi: 2
birinci.txt uzunluk=25
ikinci.txt uzunluk=41
```

`kaynakDizini.ReadDir("kaynaklar")` çağrısı, sanki gerçek bir dizini listeliyormuş gibi
davranıyor — dosya adlarını, dosya olup olmadıklarını, boyutlarını veriyor — ama listelediği
şey diskte değil, ikilinin içinde. Bu, `embed.FS` tipinin standart kitaplığın dosya sistemi
soyutlamasını (`io/fs` paketindeki `fs.FS` arayüzü) karşıladığı için mümkün; bir dosya sistemi
gibi davranan her araç, kaynağın gerçek disk mi yoksa ikilinin içine gömülmüş bir kopya mı
olduğunu bilmeden çalışabiliyor. Bu arayüz ayrımı Ağ ve Veri Biçimleri konusunda kurulan
sunucu tarafı ayrıntılarla burada tekrar kurulmuyor; ölçülen tek şey, gömülü dizinin gerçek
bir dizin gibi listelenip okunabilmesi.

Bu, önceki dersteki `io.Reader`/`io.Writer` bileşiminin bir başka örneği: `fs.FS` da tek
birkaç yöntemlik dar bir arayüz, ve onu bekleyen bir işlev `os.DirFS("bir/gercek/yol")`
ile de, `embed.FS` ile de, testler için elle yazılmış bir sahte dosya sistemiyle de
çalışabiliyor. `kaynakDizini` değişkeninin somut tipi `embed.FS`, ama onu `fs.FS` bekleyen
bir işleve verdiğinizde o işlev içeriğin gerçek diskte mi yoksa ikilinin içinde mi
durduğunu hiç bilmiyor — tıpkı bir önceki dersteki `oku` işlevinin kaynağın dosya mı
arabellek mi olduğunu bilmemesi gibi.

## Derleme Zamanı Sınırları

Gömme yönergesinin çalışabilmesi için hedef dosyanın derleme **sırasında**, paketin kendi
dizininde bulunması gerekiyor. Bu iki kısıtı ayrı ayrı sınamak, önceki derste kurulan
`derlenirMi` biçimindeki bir yardımcıyla mümkün: kaynağı geçici bir dizine yazıp derlemeyi
deneyen, sonucu `derlendi`/`derlenmedi` diye sınıflayan bir işlev.

**SS7.** — Üç kaynak deneniyor: dosyası gerçekten var olan bir gömme, dosyası hiç var olmayan
bir gömme, ve paketin kendi dizininin dışına (`../`) çıkmaya çalışan bir gömme. Derleyicinin
hata metni burada da basılmıyor; yalnızca ret sınıfı okunuyor.

```go
// derlenir-mi.go — gomme yonergesinin derleme zamani kisitlari, derlenirMi ile sinaniyor
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

func derlenirMi(kaynak string, digerDosyalar map[string]string) bool {
	dizin, err := os.MkdirTemp("", "sinama")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dizin)
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dizin, "kod.go"), []byte(kaynak), 0o644)
	for ad, icerik := range digerDosyalar {
		os.MkdirAll(filepath.Dir(filepath.Join(dizin, ad)), 0o755)
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func kaynakUret(dosyaAdi string) string {
	return fmt.Sprintf(`package main
import (
	_ "embed"
)
//go:embed %s
var veri string
func main() {}
`, dosyaAdi)
}

func main() {
	varOlan := derlenirMi(kaynakUret("var.txt"), map[string]string{"var.txt": "icerik"})
	olmayan := derlenirMi(kaynakUret("yok.txt"), nil)
	paketDisi := derlenirMi(kaynakUret("../disari.txt"), nil)

	fmt.Println("var olan dosya gomuluyor    :", varOlan)
	fmt.Println("olmayan dosya gomuluyor     :", olmayan)
	fmt.Println("paket disi dosya gomuluyor  :", paketDisi)
}
```

```
var olan dosya gomuluyor    : true
olmayan dosya gomuluyor     : false
paket disi dosya gomuluyor  : false
```

Üç denemenin ilki derleniyor, öteki ikisi derlenmiyor — ama iki ret de aynı nedenden değil.
Olmayan dosya, yönergenin en temel koşulunu karşılamıyor: gömülecek içerik hiç yok. Paket
dışı dosya ise içerik var olsa bile reddediliyor, çünkü gömme yönergesi yalnızca **kendi
paketinin dizini ve altındaki** dosyalara erişebiliyor; bir üst dizine çıkan bir yol, dosya
gerçekten orada dursa bile derleme zamanında kabul edilmiyor. Bu, dosya okuma ile gömme
arasındaki bir başka fark: `os.ReadFile` çağıran kodun erişebildiği her yola gidebilirken,
gömme yönergesi paketin kendi sınırlarının dışına hiç çıkamıyor.

Bu sınırın kendisi keyfi değil: bir paket, kendi dizininin dışına referans veren bir gömme
yönergesi taşırsa, o paketi başka bir yerden (başka bir modülden, başka bir makineden) alıp
derleyen kişi kaynağın nerede durduğunu bilemez — üst dizin, paketle birlikte taşınan bir
şey değil. Paketin kendi dizinine hapsedilmiş bir gömme, paketin **kendi kendine yeterli**
olmasını garanti ediyor: paketi kopyalayan biri, gömülü kaynakların tamamını da otomatik
olarak kopyalamış oluyor.

Bir başka kısıt burada koşturulmuyor ama belirtmeye değer: adı nokta ya da alt çizgiyle
başlayan dosyalar (`.gizli`, `_yardimci`) yönergenin varsayılan örüntüsüne dahil olmuyor;
onları da gömmek isteyen bir yönergenin başına `all:` öneki eklenmesi gerekiyor. Bu da aynı
ailenin bir parçası: gömme yönergesi, hangi dosyaların "gerçekten kaynağın parçası" sayılacağı
konusunda kendi varsayılan kurallarını taşıyor.

## Özet

- `//go:embed` yönergesi bir dosyanın içeriğini derleme sırasında bir Go değişkenine
  yerleştiriyor; hedef tipe göre (`string`, `[]byte`, `embed.FS`) farklı bir temsil üretiyor.
  Sınırdan geçen şey yine bayttır; tipi **hedef tipin kendisi yeniden kuruyor**.
- Aynı dosyaya bağlanan üç değişken birbirine ve diskteki dosyanın canlı okumasına birebir
  eşit içerik veriyor — dosya hâlâ diskteyken.
- Sınırlayıcı ölçüm: derlenmiş bir ikili, kaynak dosya silindikten sonra da gömülü içeriği
  doğru üretiyor — bağ derleme zamanında kuruluyor, koşum zamanında kaynağa bir daha hiç
  ihtiyaç duyulmuyor, ve yalnızca canlı bir disk okuması (`os.ReadFile`) başarısız oluyor.
- Bir örüntü, birden çok dosyayı tek bir `embed.FS` değişkenine gömebiliyor; sonuç `fs.FS`
  arayüzünü karşılayan, gerçek bir dizin gibi listelenip okunabilen bir yapı oluyor.
- Gömme yönergesi derleme zamanında iki kısıtla sınırlı: hedef dosya gerçekten var olmalı ve
  paketin kendi dizininin dışına çıkamaz; ikisi de derlemeyi durduruyor.
- "Gömme" burada Bileşik Tipler konusundaki yapı gömmeden ayrı bir düzenek: biri kod içi bir
  ilişki, öteki derleyicinin dosyadan değişkene kurduğu bir bağ.

## Sonraki Adım

Bu derste ölçülen bağ zamanla ilgiliydi ama zamanın kendisi hiç ölçülmedi — yalnızca "önce
derlendi, sonra silindi, sonra çalıştırıldı" sırası önemliydi. Bu sıralama, sınırı geçen bir
değerin ne taşıdığı sorusuna konunun ilk iki dersinin verdiği yanıtı tamamlıyor: dosya, arabellek
ve dizgi tip bilgisini hiç taşımıyordu; gömülü kaynak ise içeriği taşıyor ama bunu sınırı
**koşum sırasında** değil, **derleme sırasında** geçerek yapıyor — sınırın kendisi zamanla
kayıyor.

Sıradaki ders zamanın kendisini, bir değerin metne dönüp geri geldiğinde neyin taşındığını ve
neyin kaybolduğunu soruyor: bir an, bir saat dilimine bağlandığında ne kazanıyor, metne
yazılıp geri okunduğunda bu kazanımdan ne kalıyor? Burada gömme yönergesinin verdiği "içerik
sabit" güvencesinin bir karşılığı yok — zaman, tanımı gereği ölçüldüğü ânın kendisiyle
sınırlı, ve o ânı bir dizgiye yazıp geri okumak, gömülü bir dosyayı okumaktan farklı bir kayıp
türü doğuruyor.
