Ders 06 / 12
WebAssembly Hedefi
Aynı kaynağın koştuğu hedefe göre farklı bir gövdeye ihtiyaç duyması: standart kütüphanenin bir parçasına (dosya sistemi) dayanan bir gövde ile hiç dayanmayan bir gövde koşullu derlemeyle ayrılır, ikisi de yerel hedefte koşturulur ve aynı sonucu verir.
Yayın Güncelleme
Bu derste
- Koşullu derlemenin, hedefe göre değişen bir kararı kaynakta nasıl ayırdığını yerel hedefte koşturarak gösterir
- Dosya sistemine dayanan bir gövde ile dayanmayan bir gövdenin aynı sonucu ürettiğini karşılaştırarak kanıtlar
- Kaynağın taşınabilir görünmesinin taşınabilir olduğu anlamına gelmediğini, hedef bağımlılığının kaynaktan okunması gerektiğini açıklar
İçindekiler
Önceki ders, sınırın iki dil arasında olduğu durumu ölçtü: bildirim doğruysa, hangi işlemcide ya da işletim sisteminde koştuğunun önemi yoktu. Bu ders aynı bağımsızlığı tersine çeviriyor. Burada dil aynı kalıyor — kaynağın tamamı Rust — ama koştuğu hedef değişiyor ve bazı gövdeler bu değişikliğe kayıtsız kalamıyor. Bir tarayıcıda koşan bir hedef, bir dosya sistemine, bir işletim sistemi iş parçacığına ya da bir saate her zaman erişemez; bu ders o erişimin kaynakta nasıl görünür kılındığını ölçüyor.
Önceki dersteki sınır bir bildirimin doğruluğuna, buradaki sınır ise hedefin sunduğu ortamın kendisine bağlı; ikisi de derleyicinin doğrudan sınayamadığı bir varsayıma dayanıyor. Bu kısıt Rust’a özgü değil, tarayıcının kendi güvenlik modelinden geliyor. Bir tarayıcı sekmesi, çalıştırdığı kodu diskten, ağdan ve işletim sisteminden yalıtılmış bir alanda tutar; sayfanın kendi başına bir dosya açması, bir iş parçacığı oluşturması ya da bir soket kurması, yalıtımın amacına aykırı düşer. Bu alana derlenen bir program, o alanın sunduğu olanaklarla sınırlıdır — ve bu olanaklar, aynı programın bir sunucuda ya da bir masaüstünde koşan sürümünün sahip olduklarından daha dardır. Rust’ın standart kütüphanesi bu farkı gizlemez: dosya sistemi, iş parçacığı ve zaman gibi modüllerin bir kısmı, bu yalıtılmış hedefte ya hiç yoktur ya da farklı davranır.
Bu derste yalnız yerel hedef derlenir. Başka bir hedef için gerçek bir derleme
yapılmıyor; hedefe göre değişen karar, koşullu derleme ile yerel hedefte gösteriliyor
— kaynaktaki iki gövdeden hangisinin bu makinede seçildiği koşturularak görülüyor, öteki
gövde ise hiç derlenmeden kaynakta duruyor. Bu seçim çalışma zamanında yapılmaz: cfg
tarafından yönetilen karar, program çalışmaya başlamadan önce, derleme sırasında verilir.
Aynı ikili dosyanın içinde iki gövdeden birini seçip çalışma anında diğerine geçmek
mümkün değildir — hangi hedef için derlendiyse, o hedefin gövdesi ikili dosyaya girer,
öteki hiç girmez.
Hedefe Göre Ayrılan Gövde
#[cfg(...)] bir çalışma zamanı dallanması değildir; bir if ifadesiyle karıştırılmamalı.
if çalışma anında bir koşulu sınar ve iki dalın da ikili dosyada yer aldığı, yalnız
birinin çalıştırıldığı bir yapıdır. cfg ise derleme anında karar verir: koşulu
sağlamayan öğe, kaynak ağacından hiç kesilmiş gibi, derlemenin geri kalanına hiç
girmez. Bu farkın sonucu, aşağıdaki koşumda görülüyor.
Aşağıdaki koşum bir sensör kaydını okuyan iki gövde tanımlıyor. Biri dosya sisteminden
okuyor; öteki, tarayıcı hedefinde derlenecek bir gövdenin yerini tutuyor ve bu makinede
hiç derlenmiyor. Hangi gövdenin derleneceğine #[cfg(...)] özniteliği karar veriyor.
DS11. Sensör kaydı dört satırlık bir metin: 12, 7, 5, 21 — toplamı 45.
// wasm1.rs — hedefe gore ayrilan govde, yerel hedefte secilen dal
use std::fs;
#[cfg(not(target_family = "wasm"))]
fn kayitlari_oku() -> String {
// Dosya sistemine dayanan govde: yalnizca dosya sistemi olan hedeflerde derlenir.
fs::read_to_string("sensor.txt").expect("dosya okunamadi")
}
#[cfg(target_family = "wasm")]
fn kayitlari_oku() -> String {
// Bu govde tarayici hedefinde derlenir; yerel hedefte hic derlenmez,
// bu yuzden burada tanimlanmamis bir cagri tasimasi hicbir sey bozmaz.
govde_wasm_hedefinde_baska_bir_kaynaktan_okur()
}
fn toplam(metin: &str) -> i32 {
metin.lines().filter_map(|s| s.trim().parse::<i32>().ok()).sum()
}
fn main() {
fs::write("sensor.txt", "12\n7\n5\n21\n").expect("dosya yazilamadi");
let icerik = kayitlari_oku();
println!("dosya sistemine dayanan govde toplami: {}", toplam(&icerik));
}
dosya sistemine dayanan govde toplami: 45
Kaynakta iki kayitlari_oku tanımı var, ama derlenen program yalnız birini içeriyor.
#[cfg(not(target_family = "wasm"))] işaretli gövde, bu makine wasm ailesinden
olmadığı için derlemeye girdi; #[cfg(target_family = "wasm")] işaretli gövde ise hiç
derlenmedi — içindeki çağrı hiçbir yerde tanımlı olmadığı hâlde derleyici bunu fark
etmedi, çünkü o gövdeyi hiç görmedi. DS12. Bu, cfg ile dışarıda bırakılan bir gövdenin
söz dizimi geçerli olduğu sürece hiçbir semantik denetimden geçmediğini gösteriyor;
“derlenmeyen” ile “yanlış” aynı şey değil, derlenmeyen gövde hakkında derleyici hiçbir
yargı vermiyor.
Bu, önceki dersteki yabancı işlev bildirimine benziyor görünse de ayrı bir güvence
taşıyor. extern "C" bildirimi, derleyicinin hiç görmediği ama gerçekten var olan ve
bağlanması gereken bir gövdeyi tarif ediyordu; bağlayıcı o adı bulamazsa derleme çöküyordu.
#[cfg(target_family = "wasm")] ile işaretli gövde ise bu makinede hiç aranmıyor —
içinde geçen govde_wasm_hedefinde_baska_bir_kaynaktan_okur adının bu programın hiçbir
yerinde tanımlı olmaması önemli değil, çünkü o ad hiçbir zaman bağlayıcıya ulaşmıyor.
Yabancı fonksiyon arayüzünde devredilen güvence bir bildirimin doğruluğuydu; burada
devredilen hiçbir şey yok, çünkü o gövde bu derlemenin bir parçası bile değil.
kayitlari_oku içindeki .expect(...) çağrısı da bilinçli bir seçim: dosya, aynı
main işlevi içinde bir satır önce yazıldığı için okuma başarısız olmayacağı biliniyor,
ama Rust bunu derleme zamanında bilemiyor — read_to_string her zaman bir Result
döndürür ve bu değer ele alınmak zorunda. .expect(...) bu değeri açıp, başarısızlık
durumunda panikle durmayı seçiyor; bu ders panik tetiklemediği için bu yol yalnızca
başarı durumunda izleniyor.
Yukarıdaki koşum target_family = "wasm" yazımını seçiyor; dilin tanıdığı öznitelik
adlarından biri de target_arch = "wasm32"dir ve ikisi aynı şeyi sormaz. target_family,
bir hedefin ait olduğu geniş aileyi (wasm ailesi, unix ailesi gibi) sorar; target_arch
ise işlemci mimarisinin adını (wasm32, x86_64 gibi) sorar. Bu derste aile sorulmuştu,
çünkü ölçülen ayrım mimariye değil, hedefin sunduğu ortam sınıfına dayanıyordu — dosya
sistemi bulunmayan bir hedef, tek bir mimariyle sınırlı değildir. Hangi öznitelik adının
seçildiği kaynaktan okunabilir bir karardır ve gövdenin hangi soruya göre ayrıldığını
söyler.
Dosya Sistemine Dayanmayan Karşılık
Bir gövdenin cfg ile ayrılması, o gövdenin varlığını sürdürmesi gerektiği anlamına
gelmez. Aynı sensör kaydı, dosya sistemine hiç dokunmadan da elde edilebilir — bu ders
için değer kaynağın içine gömülü. Bu iki seçenek arasındaki fark bir uygulama ayrıntısı
değildir: ilki verinin çalışma zamanında bir kaynaktan geldiğini varsayar, ikincisi
verinin derleme zamanında zaten bilindiğini varsayar. Hangi varsayımın geçerli
olduğu, çözülen problemin kendisine bağlıdır — sensör kaydı gerçekten değişen bir veriyse
dosya (ya da onun yerini tutan başka bir kaynak) gerekir; sabit bir yapılandırma değeriyse
gömülü sabit yeterlidir.
// wasm2.rs — dosya sistemine hic dayanmayan, hedeften bagimsiz govde
const GOMULU_KAYITLAR: &str = "12\n7\n5\n21\n";
fn toplam(metin: &str) -> i32 {
metin.lines().filter_map(|s| s.trim().parse::<i32>().ok()).sum()
}
fn main() {
let toplam_deger = toplam(GOMULU_KAYITLAR);
println!("gomulu govde toplami: {}", toplam_deger);
}
gomulu govde toplami: 45
İki gövde de aynı sonucu (45) verdi, ama farklı yollardan. Dosya sistemine dayanan
gövdenin çalışması için ortamın bir dosya sistemi sunması gerekiyordu — bu makinede
sunuluyor, ama bir tarayıcı hedefinde bu varsayım genellikle geçerli değildir, çünkü
tarayıcı sayfası doğrudan bir dosya sistemine erişemez. Gömülü gövde ise hiçbir dış
kaynağa bağlı değil; verisi kaynağın kendisinde, derleme anında sabitleniyor. DS13.
Aynı sonucu üreten bu iki yazım, hedef bağımsız bir görevi (dört sayıyı toplamak) hedefe
bağlı bir kaynaktan (dosya) ve hedeften bağımsız bir kaynaktan (gömülü sabit) besleyerek
gösteriyor.
Verinin kaynağa gömülmesi burada const bildirimiyle yapıldı; değer derleme sırasında
ikili dosyanın bir parçası olur, çalışma zamanında hiçbir dış kaynak istemez. Bu yazım
yalnızca tarayıcı hedefiyle sınırlı bir çözüm değildir — bir dosya sisteminin hiç
bulunmadığı, yalnızca sabit bir bellek düzeninin var olduğu ortamlar için de aynı
yaklaşım kullanılır. Sıradaki ders bu ortamı doğrudan ele alacak; oradaki kısıt tarayıcının
yalıtılmış alanının değil, standart kütüphanenin kendisinin yokluğudur.
İkinci Bir Ayrım: İş Parçacığı
Dosya sistemi tek örnek değil. Aynı kalıp, birden çok iş parçacığı açma yeteneği için de
geçerli — bir tarayıcı sayfasının çalıştırdığı kod, işletim sisteminin sıradan iş
parçacığı oluşturma çağrısına her zaman erişemez. Bir iş parçacığı açmak, işletim
sisteminden yeni bir yürütme birimi ayırmasını ve bu birimi zamanlamasını ister; bu,
dosya açmaktan farklı bir hizmet ama aynı sınıftan bir istek — ikisi de tek başına
kaynağın içinde yürütülemez, ortamın bir şey sağlamasını bekler. Aşağıdaki koşum aynı
cfg düzenini bir kez daha kuruyor: yerel hedefte derlenen gövde bir iş parçacığı açıp
sonucunu topluyor, wasm ailesi için yazılmış gövde ise yine bu makinede hiç derlenmiyor.
DS14. Hesaplanan değer 6 * 7, yani 42; iş parçacığının döndürdüğü değer ana
gövdeye join çağrısıyla toplanıyor.
// wasm3.rs — is parcasina dayanan govde, yerel hedefte secilen dal
use std::thread;
#[cfg(not(target_family = "wasm"))]
fn deger_uret() -> i32 {
// Is parcasi acan govde: coklu is parcasi destegi olmayan hedeflerde derlenmez.
let is_parcasi = thread::spawn(|| 6 * 7);
is_parcasi.join().expect("is parcasi cokmus olamaz")
}
#[cfg(target_family = "wasm")]
fn deger_uret() -> i32 {
wasm_hedefinde_ayri_bir_yontemle_hesaplanir()
}
fn main() {
println!("is parcasina dayanan govde: {}", deger_uret());
}
is parcasina dayanan govde: 42
Bu koşumda da fark aynı yerde: thread::spawn çağrısı ayrı bir iş parçacığı açılmasını
işletim sisteminden ister ve bu istek her hedefte karşılanamaz. #[cfg(not(target_family = "wasm"))] etiketi bu bağımlılığı üstlenen gövdeyi işaretliyor; aynı hesabı doğrudan,
hiçbir iş parçacığı açmadan yapan bir gövde de yazılabilirdi (6 * 7 tek satırda
hesaplanır) ve o gövde her hedefte aynı şekilde çalışırdı. Buradaki nokta önceki
ölçümle aynı: hedefe bağlı olan, hesabın kendisi değil, hesabı nasıl yürüttüğüdür.
Bu iki örnek (dosya, iş parçacığı) arasında bir de fark var: dosya sisteminin yokluğu
genellikle bir hata değeriyle kendini gösterir — çağrı bir Result döndürür ve başarısız
olursa bu değer üzerinden anlaşılır. İş parçacığı açma yeteneğinin durumu hedefe göre
değişir; bazı hedeflerde işlev hiç bulunmaz ve kaynak o işlevi kullanan haliyle
derlenmez, bazı hedeflerde ise farklı bir kısıtla (örneğin tek bir iş parçacığıyla
sınırlı olarak) çalışabilir. İki durumda da ortak olan, bu farkın kaynakta bir yerde
görünür olması gerektiğidir — ya bir cfg etiketiyle, ya da dönen değerin ele alınma
biçimiyle.
Sınırlayıcı Ölçüm
Yukarıdaki iki koşum da bu makinede aynı sonucu verdi — ama bu, dosya sistemine dayanan
gövdenin her hedefte çalışacağı anlamına gelmez. Kaynağın kendisi, hangi kısmının hedefe
bağlı olduğunu zaten söylüyor: #[cfg(not(target_family = "wasm"))] etiketi, altındaki
gövdenin wasm ailesi dışındaki hedeflerde derleneceğini, #[cfg(target_family = "wasm")]
etiketi ise karşıt gövdenin yalnız wasm hedefinde derleneceğini beyan ediyor. Bu bilgiye
ulaşmak için o hedefte bir koşum çalıştırmaya gerek yok; etiketin kendisi, koşuma
bakmadan okunabilen bir bildirim.
Bunun tersi de doğru: cfg etiketi taşımayan bir gövde, otomatik olarak her hedefte
çalışacağı anlamına gelmez. toplam işlevi bu derste hiçbir cfg taşımıyor ve iki
gövde tarafından da paylaşılıyor — çünkü yalnız metin ayrıştırma ve toplama yapıyor, dış
bir kaynağa dokunmuyor. Ama bir işlev cfg taşımadığı hâlde içinde iş parçacığı açan,
gerçek zamanı okuyan ya da bir soket bağlantısı kuran bir çağrı taşısaydı, bu bağımlılık
kaynaktan görünmezdi — derleyici böyle bir çağrıyı reddetmez, yalnızca o hedefte
derlerken ya derleme hatası verir ya da çalışma zamanında beklenmeyen bir sonuç üretir.
Kaynağın taşınabilir görünmesi, taşınabilir olduğunu göstermez; hangi varsayımın hedefe
bağlı olduğu ancak kaynaktaki cfg etiketlerinden ve kullanılan standart kütüphane
parçalarından okunabilir, bir koşumdan değil.
Bu derste ölçülen iki örnek (dosya sistemi, iş parçacığı) tükenmez bir liste değil, aynı
sınırın iki yüzü. Gerçek zamanın okunması da benzer bir sınıra girer: bir hedefin sistem
saatine erişimi olmayabilir ya da bu erişim farklı bir yolla sağlanabilir. Üç örneğin
ortak yanı, hepsinin işletim sisteminden ya da donanımdan bir hizmet istemesidir —
dosya sistemi bir depolama hizmeti, iş parçacığı bir zamanlama hizmeti, saat bir donanım
sayacı hizmeti ister. Yalıtılmış bir hedef bu hizmetlerin bir kısmını hiç sunmaz ya da
farklı bir arayüzden sunar; kaynak, hangi işlevin hangi hizmeti istediğini cfg
etiketleriyle ya da kullanılan modülün kendisiyle işaretlediği ölçüde okunabilir kalır.
Üçü de bu derste koşturulmuyor; yalnızca dosya sistemi ve iş parçacığı örnekleri
koşturuldu, saat örneği aynı sınıfa ait olduğu için yalnızca anılıyor.
Bu okunabilirlik, gerçek çok hedefli kaynakların (birden çok işletim sistemi ve mimaride
derlenmesi beklenen kütüphanelerin) neden cfg etiketleriyle dolu göründüğünün de
nedenidir; bu ders boyunca kurulan iki örnek, o kaynaklardaki düzenlerin küçük ölçekli
bir modelinden ibaret. Bir kaynağı bir hedefe taşımadan önce güvenilir bir yöntem, kaynaktaki cfg
etiketlerini ve dış hizmet isteyen çağrıları taramaktır — bu derste yapılan tam olarak
budur, yalnızca tek bir hedef çiftinde (yerel hedef ile wasm ailesi) ve tek bir gövde
üzerinde.
Özet
- Hedefe göre değişen bir karar,
#[cfg(...)]öznitelikleriyle kaynakta iki ayrı gövdeye bölünebilir; yerel hedefte yalnız biri derlenir, öteki hiç derlenmeden kaynakta durur —cfgbirifgibi çalışma anında dallanmaz, derleme anında karar verir - Tarayıcı hedefi için gerçek bir derleme yapılmadı; hedefe bağlı karar kaynak üzerinden modellendi
- Dosya sistemine dayanan gövde ile dayanmayan gövde aynı sensör toplamını (
45) verdi; fark sonuçta değil, hangi ortam varsayımına dayandıklarında - Bir tarayıcı hedefinin dosya sistemi gibi bazı ortam olanaklarını sunmaması, bu hedefin verdiği güvencenin dilin kendisinden değil hedeften geldiğini gösterir — sınıf dilin dışında
- Dosya sistemi ile iş parçacığı açma aynı sınırın iki ayrı örneği: ikisi de işletim sisteminden bir hizmet ister ve bu hizmet her hedefte aynı biçimde sunulmaz
- Kaynağın taşınabilir görünmesi taşınabilir olduğu anlamına gelmez; hedef bağımlılığı
cfgetiketlerinden ve kullanılan standart kütüphane parçalarından, koşumdan değil, kaynaktan okunur
Sonraki Adım
Bu derste sınır, aynı dilin iki hedefi arasındaydı; fark, ortamın hangi olanakları sunduğuydu — dosya sistemi de iş parçacığı da işletim sisteminin var olduğu varsayımına dayanıyordu, yalnızca o varsayımın karşılanıp karşılanmadığı hedeften hedefe değişiyordu. Sıradaki ders bu sınırı bir uca taşıyor: standart kütüphanenin hiç bulunmadığı, işletim sisteminin kendisinin olmadığı bir ortam. Orada gömülü sabit gibi hedeften bağımsız bir yazım bile, artık bellek ayırmanın kendisinin var olup olmadığı sorusuyla karşılaşacak — bu derste “hedefe bağlı” olan yalnız birkaç işlevdi, orada “hedefe bağlı” olan, programın bütün gövdesinin nasıl yazılacağını belirleyen bir kısıttır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.