- Zig 0.15 sürümünde yeni IO arayüzü (
std.Io.Reader, std.Io.Writer) kullanıma sunuldu
- Amaç, önceki IO yaklaşımındaki karmaşıklık ve performans sorunlarını iyileştirmekti; ancak gerçek kullanım biçimi konusunda kafa karışıklığı doğdu
tls.Client ve buffer kullanımıyla ilgili, parametrelerin tutarsız aktarım biçimi bu kafa karışıklığını artırıyor
- En temel kullanım örneklerini kurarken bile farklı buffer boyutları ve seçenek alanları belirtme gibi karmaşık gereksinimler var
- Resmî belgeler, kod örnekleri ve yardımcı fonksiyonların eksikliği nedeniyle bu yapı yeni başlayanlar için sezgisel değil
Zig 0.15'te sunulan yeni IO arayüzü ve arka planı
- Zig 0.15 sürümünde
std.Io.Reader ve std.Io.Writer adlı yeni IO tipleri kullanıma sunuldu
- Önceki IO arayüzü, performans sorunları, tiplerin karışması ve
anytype kullanımının aşırılığı nedeniyle karmaşıklığa yol açıyordu
- Yeni IO yapısında arayüzler arasında daha net tip ayrımı ve performans iyileştirmesi başlıca hedefler
tls.Client ve IO arayüzü kullanımındaki pratik sorunlar
- Mevcut smtp kütüphanesini güncellerken
tls.Client.init fonksiyonunun kullanımında kafa karışıklığı yaşanıyor
- Belgelerde
init fonksiyonunun Reader ve Writer pointer'ları ile bir seçenekler kümesini argüman olarak aldığı belirtiliyor
- Zig'in
net.Stream yapısı sırasıyla reader() ve writer() metodlarıyla Stream.Reader/Writer döndürüyor
- Ancak
Stream.Reader/Writer ile std.Io.Reader/Writer tam olarak aynı tipler olmadığı için dönüştürme gerekiyor
- Reader için
interface() metodunu çağırmak, Writer içinse &interface alanını kullanmak gerektiğinden tutarlılık eksikliği hissediliyor
Buffer ve seçenek alanlarını ayarlama sorunu
stream.writer, stream.reader sırasıyla buffer'ı argüman olarak alıyor
- Buffer'ın yeni IO arayüzünde zorunlu bir unsur olduğu özellikle vurgulanıyor
tls.Client.init çağrısında ca_bundle, host, write_buffer, read_buffer gibi dört seçenek alanı mutlaka gerekli
- Seçenek parametresi içinde verilen değerlerle doğrudan argüman olarak verilen değerlerin nasıl ayrıldığı belirsiz hissettiriyor
var tls_client = try std.crypto.tls.Client.init(
reader.interface(),
&writer.interface,
.{
.ca = .{.bundle = bundle},
.host = .{ .explicit = "www.openmymind.net" } ,
.read_buffer = &read_buf2,
.write_buffer = &write_buf2,
},
)
- Pratikte buffer pointer'ları doğru verilmezse program düzgün çalışmayabiliyor; takılma, çökme gibi çeşitli sorunlar ortaya çıkabiliyor
Reader kullanırken sezgisellik sorunu
tls.Client'ın reader alanı kendi başına "şifresi çözülmüş akış" olsa da, gerçekte std.Io.Reader içinde alışıldık bir read metodu bulunmuyor
- Bunun yerine
peek, takeByteSigned, readSliceShort gibi daha az sezgisel metodlar sunuluyor
- Kullanıma en yakın görünen API ise
stream metodu üzerinden buffer'a veri okuma biçimi
var buf: [1024]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
const n = try tls_client.reader.stream(&w, .limited(buf.len));
Tüm kod örneği ve gerçek dünya sorunları
- Baştan sona çalışan en küçük örneği oluşturmaya çalışırken bile seçenekler, buffer boyutları ve tip dönüşümleri gibi birçok ayrıntıya dikkat etmek gerekiyor
- Test, belge ve örnek eksikliği öğrenme zorluğunu ve giriş bariyerini artırıyor
- Zig dilindeki tutarlılığı ya da alttaki tasarımı yeterince anlamayanlar için garip gelen pek çok nokta var
- Standart kütüphane içinde bile bu yaklaşım yaygın kullanılmadığından, gerçek kullanım için başvurulacak kaynaklar sınırlı
Deneyim ve sonuç
std.fmt.printInt gibi adlandırma değişiklikleri ve API tasarımındaki dönüşümler nedeniyle migration sürecinin kendisi de kolay değil
reader.interface(), &writer.interface kullanımı, seçeneklerin aktarım biçimi ve birden fazla buffer gerekliliği gibi tekrar eden birçok zorluk yaşanıyor
- TLS gibi ağ/güvenlik protokollerine aşina olmayan biri açısından gereksinimleri kavramak daha da zor gelebiliyor
- Genel olarak, önceki sürümlere kıyasla açıklık, dokümantasyon ve kullanım kolaylığı açısından hâlâ yetersiz kalan birçok nokta bulunuyor
1 yorum
Hacker News görüşü
Yazar olduğumu belirteyim. Sonunda düzgün çalışır hale getirdim. Hem şifreleme writer'ı hem de stream writer için
flushsüreci gerekiyormuş, aynı zamanda okuma tarafında da sorun varmış. Streaming çalışıyor amaWriter.Fixed,sendFileuygulamadığı için ilk okumada her zaman 0 döndürüyor. İlk çağrıdan sonra içerde streaming modundan okuma moduna geçiyor ve bir anda her şey çalışmaya başlıyor (ilgili kod bağlantısı: Zig File.zig #L1318). Şimdi websocket kütüphanesinde sıkıştırma özelliğini yeniden açmaya çalışıyorum"Flush'ı unutmayın" YouTube meme'ini hatırlattı (YouTube videosu)
En az şaşırtma ilkesi (principle of least surprise) acaba nereye gitti diye merak ediyorum
Önceki arayüzden şu anki duruma gelinmiş olması gerçekten etkileyici. Büyük bir şaşkınlık var
Zig PM'i değilim ama OP'nin yaşadığı sorun için bariz ilk çözüm daha iyi dokümantasyon ve daha fazla kullanım örneği hazırlamak olurdu (çok fazla olması sorun değil). Bu tür bir çalışma, kullanıcılardan fazla şey isteyip istemediklerini sorgulamak için de iyi bir fırsat olabilir. Hedef mutlak performans ya da performans kaybı getiren soyutlamalardan kaçınmak idiyse, sanırım bu hedefe ulaşılmış; ama DX (geliştirici deneyimi) uzaya fırlamış gibi duruyor
Zig topluluğunun kültürünü pek bilmiyor gibisiniz. Dokümantasyon eksikliğinden şikâyet ederseniz, herkesin "stdlib kodunu kendin oku" diye yorum yapmaya hazır olduğunu görürsünüz. Çoğu API bu yazıdaki gibi kullanımı zor ve HTTP ya da dosya sistemi gibi temel işler bile tanıdık değilse gerçekten çok zorlayıcı oluyor. Bu yüzden gerçekten yetenekli olanlar ayakta kalıyor
Dokümantasyon yazmanın bir maliyeti var ve zaman alıyor. O süre içinde Zig'in başka kısımları da geliştirilebilir. Üzerinde çalışılan kodsa, tam olarak oturmadan dokümantasyonu ertelemek de makul bir tercih olabilir. Elbette dokümantasyon iyidir ama yeni özellikler, kritik hata düzeltmeleri ve dokümantasyon arasında öncelik belirlemek gerektiğinde her şeye aynı anda sahip olamazsınız
Zig, ne yapılmaması gerektiğini söylemeye fazla odaklanmış gibi görünüyor. Farklı yöntemleri ve kullanım örneklerini toplayıp iyi bir şekilde düzenleyen, anlatan bir yöne evrilmesini isterdim. Bu arayüzdeki eksik dokümantasyon bunun tipik bir örneği
İyi dokümantasyon ya da örnek yazmak ciddi emek istiyor. Şu an zig'de olan değişim hızına bakınca, daha tam oturmadan yazılan dokümanlar çok çabuk geçersiz hale geliyor
Zig geliştiricisi değilim ama Zig dokümantasyonunun fazla kısa olmasının nedenlerinden biri, dilin hâlâ genç ve sürekli evriliyor olması olabilir diye düşünüyorum. Bugün yazılan dokümanın yakında yanlış olacağını bile bile zaman ve enerji harcamanın zor geldiğini anlayabiliyorum
Zig dilinin kendisi gerçekten güzel, ama standart kütüphane hâlâ çok eksik, sürekli değişiyor, birçok açıdan yetersiz ve bazı kısımları ya fazla soyut ya da fazla düşük seviyeli. Şu anda standart kütüphane yerine doğrudan OS API'lerini kullanmanın daha iyi olduğunu düşünüyorum. Beta testeri olmaya hazır değilseniz standart kütüphaneden uzak durmanızı öneririm
Aslında ben de zig kullanırken çoğunlukla OS API'lerini tercih ediyorum.
cImportsiyi olduğu için zig tanımları yazmakla uğraşmak istemediğimde de rahatça kullanabiliyorumBana göre Zig aynı anda çok fazla şeyi yapmaya çalışıyor ve bu yüzden benim asgari kalite eşiğime bile ulaşamıyor. Kullanıcıları sert değişimlere ve deneylere katlanmaya zorlarken, yeterince insanın yatırım yapıp "1.0 öncesi bozuk olması sorun değil, bir gün düzelir" yanılsamasına inanmasına güveniyor gibi görünüyor (bence o gün hiç gelmeyecek). Deneylerinizin yükünü başkalarına bindirmek sağlıklı değil. Kararsız olduğunu önceden söylemiş olmanız ya da bağımlı olmayın demeniz de bir anda halının altından zemini çekilen insanlar için durumu değiştirmiyor. Zig'in tam olarak ne olduğunu anlamıyorum. Matklad buna machine level language diyor (ilgili röportaj: lobste.rs - Matklad röportajı), resmi sayfa ise robust, optimal, reusable general-purpose bir dil olduğunu söylüyor. Bunlar birbiriyle çelişiyor. Üstelik elle bellek yönetimi gerektirmeyen pek çok problem var; bu yüzden zig hiçbir zaman gerçekten genel amaçlı bir dil değil. Tüm bu karmaşa, zig'in kararsızlığında ve aşırı büyümüş standart kütüphanesinde kendini gösteriyor. Sadelik ve genel amaçlılık iddia ederken bu kadar büyük bir kütüphane çelişkili. Async de tüm platformlarda evrensel ve verimli biçimde uygulanabilecek bir özellik olmadığı halde sanki her derde deva gibi sunuldu. Eskiden fonksiyon renklendirme sorununu çözdüğü söylenerek tanıtılıyordu ama o girişim çoktan terk edildi. Şimdi tekrar başarabileceğine inanmamızı beklemek tuhaf. Aslında tüm platformlarda derleyici yapmak için gereken tek şey temel assembly talimatları; luajit parser'ını tamamen assembly ile yazdı ve her yerde gayet iyi çalışıyor. Ben çoğunlukla lua ile programlıyorum ve interpreter'da neredeyse hiç hataya rastlamadım. zig'in luajit'ten daha iyi çözeceği bir problem de aklıma gelmiyor. Zig ile çözülen bir şey varsa bile onu lua koduna embed edip FFI ile bağlamak mümkün. Çoğu kodun bu kadar düşük seviyeli optimizasyona ihtiyacı yok. Zig eklemek işleri daha çok baş ağrısına dönüştürüyor. Son zamanlarda zig etrafındaki abartı ile gerçeklik arasındaki fark neredeyse yapay zeka seviyesine ulaştı. Zig'e inanmak için, şu an sahip olmadığı yetenekleri bir gün kazanacağına dair boş bir umuda inanmanız gerekiyor. Ortada gerçek bir yürütme planı da yok; sadece "biraz daha bekleyin" deniyor
Bir kütüphane ya da arayüzün kendi tipimin buffer tahsisini istemesini anlamıyorum. Ben zaten parse edeceksem kütüphaneye gerek yok; kullanacaksam da bu, birlikte çalışabilirliği bozabilecek bir şey. Go'nun alışılmadık arayüzü, bazı arayüzlerin writer arayüzünü genişletmesi (örneğin hijacker arayüzü) ya da request nesnesinin farklı middleware'lerde çeşitli şekillerde yeniden kullanılması gibi nedenlerden geliyor. Kısacası request'in genişletilmesi gerekmez ama response, websocket veya tcp wrapper'ı gibi çok farklı biçimlere dönüşebilir
Kütüphanenin dış buffer tahsisi istemesi bana tuhaf gelmiyor. Bu, esneklik sağlıyor ama daha fazla elle iş yapmayı gerektiriyor. Örneğin hazır bir buffer havuzunuz varsa bunu yeniden kullanmak isteyebilirsiniz. Tip içeride kendi başına tahsis yaparsa bu mümkün olmaz. Ya da tüm kaynakların önceden tahsis edilmesi gereken ortamlarda sonradan tahsis yapılamaz. Dezavantajı şu: kullanıcıların yalnızca %10'u bu esnekliğe ihtiyaç duyarken, %90'ı sadece buffer tahsis edip verecek ve sonuçta herkes daha karmaşık bir iş yapmak zorunda kalacak. En iyi yaklaşım, yüksek esneklik sunarken basit senaryoları da kolay tutmaktır. Mesela 0 uzunlukta bir buffer (veya Zig'de
null) verilirse tipin kendi tahsisini yapması sağlanabilir; ayrıca buffersız kolay oluşturma için ek bir constructor da sunulabilir. Tabii bunun dokümantasyon açısından zahmetli olacağı açıkBu, her dilin seçtiği yerleşik kuralların farklı olmasına benziyor (radyan/derece gibi). Her türlü IO serbestçe dönüştürülebilir. Bir tarafta buna mock denir, başka bir dilde
unsafeFooadı verilebilir. Andrew Kelley, live stream'de Haskell topluluğunun 30 yılda tartıştığı örüntüleri kendi başına yeniden keşfediyor. Demek ki gelecek Zig'miş. Aydınlanmayı önce o yaşamışDış buffer'ın anlamı, fonksiyonun buffer tahsisini atlamasıdır
Zig yan projemi 0.15.x'e yükseltmeyi düşünmüyorum. Andrew'un neden bu sürümü çıkardığını ve yeni IO'yu erken benimseyenlerin eline vermesini anlıyorum. Ama readers/writers tarafındaki büyük değişikliklerin üzerinden sadece birkaç gün geçti. Standart kütüphane üzerinde çalışanlar için bu iyi olabilir, ama benim gibi hobi olarak zig kullananlar için 0.16.0'da stabil hale gelmesini beklemek daha mantıklı geliyor
Dilin adı Zig ise arada bir Zag da yapması gerekmez mi diye bir şaka geliyor aklıma
Zig çekirdek üyelerinden Loris Cro da yakın tarihli bir röportajda, IO değişikliklerinin sarsıntısı geçene kadar kendi projelerinde zig güncellemesini ertelediğini söylemişti. Ama sonrası için görünüm olumlu. Hem Andrew hem de Loris bunun son büyük değişiklik olacağını düşünüyor; bu da 1.0'ın çok uzakta olmayabileceği umudunu doğuruyor. Şu anda en büyük belirsizlik yalnızca yeniden gelecek stack-less coroutine etkisi gibi görünüyor
Yeni IO arayüzüyle ilgili yazıyı gördükten sonra zig'den uzak durmayı seçtim. Neyse ki içgüdülerimin doğru çıktığını düşünüyorum. Nedenlerim farklı olsa da sonuçta C++11 öncesinin ayrıntıcılığına benzer bir karmaşıklık hissi veriyor. Yeni bir dilin eskisinin yerine geçmeye çalışırken sonunda onun kadar karmaşık hale gelmesi gibi tanıdık bir örüntü tekrar ediyor
OP'nin,
Stream.Reader'ıstd.Io.Reader'a çevirmek içininterface()metodunu çağırmak gerekirkenStream.Writer'danstd.io.Writeralmak için&interfacealanının adresinin gerekmesini tutarsız bulması, bana göre Go topluluğunda olsaydı baştan reddedilirdi. Go, küçük değişiklikler için bile olağanüstü derin analizler yaparak karar verir. En sevdiğim Go issue örneği: Go github issue #45624. Dört yıl tartışıp sonra sonuca varıyorlar. Yavaş olabilir ama tutarlılığı, tasarım düşüncesini ve gerçek kod kullanımını çok dikkatli ele alıyorlar. Yavaş ama gerektiği kadar yavaş olduklarını düşünüyorum. Bu şekilde alınan kararlar da sonuçta çok yüksek kaliteli oluyorRust da benzer. Sadece nightly rust'ta olup stable'da olmayan pek çok faydalı özellik var (örneğin generator). Sinir bozucu olabiliyor ama stable'a giren şeyler çok derin biçimde doğrulanmış oluyor. Ben sabırsızım ama Rust ekibinin yaklaşımını sağlıklı buluyorum
Go 1.0'dan önce o kadar yavaş değildi. Daha az köklü olsa da büyük değişiklikler sık yaşanıyordu (noktalı virgüllerin kaldırılması, error type değişiklikleri vb.) ve otomatik dönüştürme araçları da vardı. 1.0 ile birlikte kararlılık sözü verildi ve bugünkü yaklaşım benimsendi
Düşük seviyeli işler için aklıma gelen ilk dillerden biri Zig. Zig'i C/C++ cross-compiler olarak da kullanabilmek çok havalı
Sorunların çoğu temelde eksik ya da yetersiz dokümantasyondan kaynaklanıyor gibi görünüyor
output.write("hello")kadar kolay görünmesi hedeflenmiş ama pratikte kullanım anlatımı yetersiz olduğu için kafa karıştırıyor. Bu kadar karmaşık bir tip sisteminin standart kütüphanede temsil edilmesi gerekip gerekmediği de şüpheli. Zig'in geneli açık, sade ve okunabilir metotlardan oluşurken yeni IO sistemi buna pek uymuyor ve sezgisel değil(zig'in yeni sistemi) aslında sadece yürütme sınırlarını ayırmak için kullanılan bir kavramı tüm runtime motoruna karıştırıyor ve iki tarafın nasıl bağlanacağını da net biçimde göstermediği için sorun yaratıyor