- Zig 0.15 वर्ज़न में नया IO इंटरफ़ेस(std.Io.Reader, std.Io.Writer) पेश किया गया
- पुराने IO तरीके की जटिलता और performance issues को सुधारना इसका उद्देश्य था, लेकिन वास्तविक उपयोग को लेकर भ्रम पैदा हुआ
- tls.Client और buffer के उपयोग से जुड़ा असंगत parameter passing तरीका भ्रम को और बढ़ाता है
- बुनियादी usage example लागू करते समय भी कई buffer sizes, option fields तय करने जैसी जटिल आवश्यकताएँ मौजूद हैं
- आधिकारिक दस्तावेज़, code examples और convenience functions की कमी के कारण यह शुरुआती उपयोगकर्ताओं के लिए सहज नहीं है
Zig 0.15 में पेश किया गया नया IO इंटरफ़ेस और उसकी पृष्ठभूमि
- Zig 0.15 वर्ज़न में std.Io.Reader और std.Io.Writer नाम के नए IO types पेश किए गए
- पिछला IO इंटरफ़ेस performance problems, type mixing, और
anytype के अत्यधिक उपयोग के कारण जटिल हो गया था
- नए IO structure में interfaces के बीच स्पष्ट type separation और बेहतर performance प्रमुख लक्ष्य हैं
tls.Client और IO इंटरफ़ेस के उपयोग में वास्तविक समस्याएँ
- मौजूदा smtp library को अपडेट करते समय tls.Client.init फ़ंक्शन के उपयोग को लेकर भ्रम पैदा हुआ
- दस्तावेज़ों के अनुसार init फ़ंक्शन Reader और Writer pointers, तथा options set को arguments के रूप में लेता है
- Zig का net.Stream क्रमशः reader() और writer() methods के माध्यम से Stream.Reader/Writer लौटाता है
- लेकिन Stream.Reader/Writer और std.Io.Reader/Writer बिल्कुल एक जैसे types नहीं हैं, इसलिए conversion की ज़रूरत पड़ती है
- Reader के लिए
interface() method call करना पड़ता है, जबकि Writer के लिए &interface field का उपयोग करना होता है, इसलिए consistency की कमी महसूस होती है
buffer और option fields सेट करने की समस्या
stream.writer, stream.reader दोनों buffer को argument के रूप में लेते हैं
- Buffer को नए IO इंटरफ़ेस में एक अनिवार्य तत्व के रूप में उभारा गया है
tls.Client.init कॉल करते समय ca_bundle, host, write_buffer, read_buffer जैसी चार option fields अनिवार्य रूप से चाहिए होती हैं
- options parameter में दिए जाने वाले मानों और सीधे arguments के रूप में दिए जाने वाले मानों के बीच विभाजन का नियम अस्पष्ट लगता है
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,
},
)
- वास्तव में यदि buffer pointers ठीक से न दिए जाएँ, तो program सही तरह से काम नहीं करता, hang हो सकता है, crash हो सकता है, या अन्य समस्याएँ आ सकती हैं
Reader उपयोग के समय सहजता की समस्या
tls.Client का reader field अपने आप में एक "decrypt किया गया stream" है, फिर भी वास्तव में std.Io.Reader में सामान्य read method मौजूद नहीं है
- इसके बजाय
peek, takeByteSigned, readSliceShort जैसे कम सहज methods ही उपलब्ध हैं
- उपयोग के सबसे क़रीब API वह है जिसमें stream method के ज़रिए buffer में data पढ़ा जाता है
var buf: [1024]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
const n = try tls_client.reader.stream(&w, .limited(buf.len));
पूरा code example और व्यावहारिक समस्याएँ
- पूरी तरह काम करने वाला न्यूनतम example बनाना हो, तब भी options, buffer size, type conversion जैसी कई बातों पर ध्यान देना पड़ता है
- tests, documents, और examples की कमी के कारण सीखना कठिन हो जाता है और entry barrier बढ़ता है
- Zig भाषा के भीतर consistency या underlying design की समझ कम हो, तो कई बिंदु अजीब लगते हैं
- standard library के भीतर भी यह तरीका बहुत अधिक इस्तेमाल नहीं होता, इसलिए व्यावहारिक reference materials कम हैं
अनुभव और निष्कर्ष
std.fmt.printInt जैसी naming changes और API design में बदलाव के कारण migration प्रक्रिया खुद आसान नहीं है
reader.interface(), &writer.interface का तरीका, options pass करने का ढंग, और कई buffers की आवश्यकता जैसी बार-बार आने वाली कठिनाइयाँ महसूस हुईं
- TLS जैसे network/security protocols से परिचित न होने पर आवश्यकताओं को समझना और भी कठिन लगता है
- कुल मिलाकर, पुराने तरीके की तुलना में clarity, documentation, और convenience में सुधार के लिहाज़ से अभी भी कई कमियाँ मौजूद हैं
1 टिप्पणियां
Hacker News राय
यह स्पष्ट कर दूँ कि मैं लेखक हूँ। आखिरकार इसे सही तरह से काम कराने में सफल हुआ। encrypted writer और stream writer, दोनों में flush प्रक्रिया ज़रूरी थी, और साथ ही read साइड में भी समस्या थी। streaming काम तो करता है, लेकिन
Writer.FixedsendFileइम्प्लीमेंट नहीं करता, इसलिए पहली read पर यह हमेशा 0 लौटाता है। पहली call के बाद यह अंदरूनी तौर पर streaming mode से read mode में स्विच करता है, और फिर अचानक सब कुछ काम करने लगता है (संबंधित कोड लिंक: Zig File.zig #L1318). अभी मैं websocket लाइब्रेरी में compression फीचर फिर से चालू करने की कोशिश कर रहा हूँइससे "Flush को मत भूलिए" वाला YouTube meme याद आता है (YouTube वीडियो)
principle of least surprise आखिर गया कहाँ, यही सोच रहा हूँ
पिछला interface छोड़कर हम इस स्थिति तक कैसे पहुँचे, यह वाकई हैरान करने वाला है। आश्चर्य बहुत ज़्यादा है
मैं Zig PM नहीं हूँ, लेकिन OP की समस्या का सबसे स्पष्ट पहला समाधान बेहतर documentation और ज़्यादा usage examples बनाना है (बहुत ज़्यादा हों तो भी ठीक है)। ऐसा काम यह देखने का अच्छा मौका भी हो सकता है कि कहीं हम users से बहुत कुछ तो नहीं करवा रहे। अगर लक्ष्य absolute performance पाना या performance घटाने वाले abstractions से बचना था, तो वह शायद हासिल हो गया है, लेकिन DX (developer experience) तो जैसे अंतरिक्ष में चला गया है
लगता है आप Zig community की संस्कृति से परिचित नहीं हैं। documentation की कमी की शिकायत करो, और तुरंत लोग "stdlib कोड खुद पढ़ो" कहने को तैयार मिलेंगे। ज़्यादातर API इस पोस्ट की तरह इस्तेमाल करने में कठिन हैं, और HTTP या file system जैसे बुनियादी काम भी अगर परिचित न हों तो बहुत मुश्किल हो जाते हैं। इसलिए अंत में वही लोग टिकते हैं जो वाकई बहुत कुशल हों
documentation लिखने में लागत भी लगती है और समय भी। उसी समय में Zig के दूसरे हिस्सों को भी बेहतर बनाया जा सकता है। अगर कोड अभी काम चल रहा हो, तो उसके स्थिर होने तक docs टालना भी एक उचित विकल्प है। documentation अच्छी चीज़ है, लेकिन जब नई features, महत्वपूर्ण bug fixes, और docs के बीच प्राथमिकता चुननी पड़े, तब हमेशा सब कुछ एक साथ नहीं मिल सकता
लगता है zig इस बात पर बहुत केंद्रित है कि क्या नहीं करना चाहिए। बेहतर होता अगर यह अलग-अलग तरीकों और usage examples को इकट्ठा करके व्यवस्थित रूप से बताने की दिशा में बढ़ता। इस interface में documentation की कमी इसका बढ़िया उदाहरण है
अच्छी documentation या examples लिखने में बहुत मेहनत लगती है। अभी zig में जितने बड़े बदलाव हो रहे हैं, उन्हें देखकर लगता है कि कुछ ठीक से जमने से पहले docs लिख भी दें तो वे जल्दी बेकार हो जाती हैं
मैं Zig developer नहीं हूँ, लेकिन मुझे लगता है कि Zig की documentation इतनी संक्षिप्त होने का एक कारण यह है कि भाषा अभी युवा है और लगातार विकसित हो रही है। यह समझ में आता है कि समय और ऊर्जा ऐसी docs पर लगाना कठिन हो, जिनके बारे में पहले से पता हो कि वे जल्द ही गलत हो सकती हैं
Zig भाषा खुद काफ़ी अच्छी है, लेकिन standard library अभी बहुत अधूरी है, लगातार बदल रही है, कई जगह कमज़ोर है, और कुछ हिस्से या तो ज़रूरत से ज़्यादा abstract हैं या फिर बहुत low-level। अभी मुझे standard library की जगह सीधे OS API इस्तेमाल करना बेहतर लगता है। अगर आप beta tester बनने के लिए तैयार नहीं हैं, तो standard library से दूर रहना बेहतर है
सच कहूँ तो मैं भी zig इस्तेमाल करते समय अधिकतर OS API ही इस्तेमाल करता हूँ।
cImportsअच्छे हैं, इसलिए जब zig definitions बनाना झंझट लगे तो उन्हें आसानी से उपयोग किया जा सकता हैमेरी नज़र में Zig एक साथ बहुत कुछ करने की कोशिश करता है, और इसी वजह से वह उस न्यूनतम quality bar तक भी नहीं पहुँच पा रहा जिसे मैं ज़रूरी मानता हूँ। जिस तरह यह users को तेज़ बदलाव और प्रयोग सहने पर मजबूर करता है, उससे लगता है कि बहुत से लोगों को इस भ्रम में निवेश करना पड़ता है कि "1.0 से पहले टूटा होना ठीक है, बाद में अच्छा हो जाएगा" (निष्कर्ष: वह दिन शायद कभी नहीं आएगा)। मुझे नहीं लगता कि अपने प्रयोगों का बोझ दूसरों पर डालना सही है। पहले से यह कह देना कि चीज़ें unstable हैं, या इन पर निर्भर मत हो, इससे उस व्यक्ति की समस्या कम नहीं होती जिसे अचानक rug pull झेलना पड़े। zig आखिर है क्या, यह भी साफ़ नहीं है। Matklad इसे machine level language कहते हैं (संबंधित इंटरव्यू: lobste.rs - Matklad इंटरव्यू), जबकि आधिकारिक पेज इसे robust, optimal, reusable general-purpose language कहता है। ये दोनों बातें एक-दूसरे से टकराती हैं। और बहुत-सी समस्याएँ ऐसी हैं जिनमें manual memory management की ज़रूरत ही नहीं होती, इसलिए zig किसी भी तरह से सचमुच general-purpose language नहीं है। यह सारी उलझन zig की instability और इसकी भारी-भरकम standard library में साफ़ दिखती है। simplicity और general-purpose होने का दावा करते हुए इतनी बड़ी library रखना विरोधाभास है। Async को भी ऐसे पेश किया गया जैसे वह हर platform पर universal और efficient समाधान हो, जबकि ऐसा संभव ही नहीं। पहले इसे function coloring समस्या का समाधान कहकर प्रचारित किया गया, फिर वह कोशिश छोड़ दी गई। उसके बाद फिर से भरोसा करने को कहना अजीब है। हक़ीक़त में हर platform पर compiler लागू करने के लिए केवल बुनियादी assembly instructions की ज़रूरत होती है; luajit ने तो parser तक pure assembly में बनाया और वह हर जगह बढ़िया चलता है। मैं ज़्यादातर programming lua में करता हूँ और interpreter में bug लगभग कभी नहीं देखता। मुझे ऐसा भी नहीं लगता कि zig कोई ऐसी समस्या हल करेगा जो luajit नहीं कर सकता। और अगर zig से ही कुछ करना हो, तो उसे lua code में embed करके FFI से जोड़ सकते हैं। ज़्यादातर code को वैसे भी उस स्तर की low-level optimization की ज़रूरत नहीं होती। zig अपनाने से उल्टा सिरदर्द बढ़ता है। आजकल zig को लेकर जो बढ़ा-चढ़ाकर उम्मीदें बनाई जा रही हैं, वे AI जैसी hype और वास्तविकता के अंतर तक पहुँच चुकी हैं। zig पर भरोसा करने के लिए आपको यह खाली उम्मीद पालनी पड़ती है कि उसमें कभी भविष्य में ऐसी क्षमताएँ आ जाएँगी जो अभी हैं ही नहीं। कोई ठोस execution plan भी नहीं, बस "थोड़ा और इंतज़ार करो" जैसी बात है
मुझे समझ नहीं आता कि कोई लाइब्रेरी या interface मेरे type से buffer allocation क्यों माँगे। अगर parsing मुझे ही करनी है, तो फिर लाइब्रेरी की ज़रूरत क्या है; और अगर लाइब्रेरी इस्तेमाल करूँ, तो यह interchangeability भी तोड़ सकता है। Go का अजीब-सा interface design इसलिए है क्योंकि कुछ interfaces writer interface को extend करते हैं (जैसे hijacker interface), और request object कई middleware में अलग-अलग तरह से reuse होता है। संक्षेप में, request को extend करने की ज़रूरत नहीं होती, लेकिन response websocket, tcp wrapper वगैरह जैसे कई रूप ले सकता है
किसी लाइब्रेरी का external buffer allocation माँगना मुझे अजीब नहीं लगता। इससे flexibility मिलती है, बदले में ज़्यादा manual काम करना पड़ता है। उदाहरण के लिए, अगर आपके पास पहले से buffer pool बना हुआ है, तो आप उसे reuse करना चाहेंगे। अगर type अंदर ही खुद allocation करे, तो यह संभव नहीं होगा। या फिर ऐसे environments होते हैं जहाँ सभी resources पहले से allocate करने पड़ते हैं, और बाद में allocation करना संभव नहीं होता। कमी यह है कि कुल users में शायद सिर्फ़ 10% को ऐसी flexibility चाहिए, जबकि 90% लोग बस buffer allocate करके दे देंगे, लेकिन जटिलता सबको झेलनी पड़ेगी। सबसे अच्छा तरीका यह होगा कि high flexibility भी मिले और simple cases में चीज़ें आसान भी रहें। उदाहरण के लिए, 0-length buffer (या Zig का
null) पास करने पर type खुद allocation कर ले, और इसके अलावा buffer के बिना simple constructor भी दिया जाए। हाँ, यह पूरी तरह documentation के लिए सिरदर्द ज़रूर हैयह कुछ-कुछ वैसा ही है जैसे हर भाषा अपनी conventions चुनती है (radian/degree वगैरह)। किसी भी IO को स्वतंत्र रूप से बदला जा सकता है। कहीं इसे mock कहते हैं, तो किसी भाषा में शायद
unsafeFooकहा जाए। Andrew Kelley ने live stream में Haskell community द्वारा 30 साल से चर्चा किए गए patterns को खुद से फिर से खोज लिया। इसलिए भविष्य Zig का है। उसे पहले ही ज्ञान मिल गयाexternal buffer का मतलब बस इतना है कि function buffer allocation छोड़ देता है
मैं अपने zig side project को 0.15.x पर ले जाने का सोच भी नहीं रहा। Andrew ने release क्यों चुना, और नया Io early adopters के हाथ में देने का फैसला क्यों किया, इसका सम्मान करता हूँ। लेकिन readers/writers में इतने बड़े बदलाव के तुरंत बाद अभी कुछ ही दिन हुए हैं। standard library पर काम करने वालों के लिए यह अच्छी बात होगी, लेकिन मेरे जैसे लोग जो शौक़ में zig इस्तेमाल करते हैं, उनके लिए 0.16.0 में stabilization के बाद तक इंतज़ार करना ज़्यादा समझदारी है
अगर भाषा का नाम Zig है, तो कभी-कभी उसे Zag भी करना चाहिए — ऐसा मज़ाक सूझता है
Zig core member Loris Cro ने भी हाल की एक इंटरव्यू में कहा कि वह अपने projects में zig update को तब तक टाल रहे हैं जब तक IO बदलावों के झटके शांत नहीं हो जाते। लेकिन आगे की तस्वीर सकारात्मक लगती है। Andrew और Loris, दोनों मानते हैं कि यह आख़िरी बड़ा बदलाव होगा, इसलिए उम्मीद है कि 1.0 अब बहुत दूर नहीं है। अभी सबसे बड़ा variable सिर्फ़ फिर से आने वाले stack-less coroutine का प्रभाव है
नए IO interface पर पोस्ट पढ़ने के बाद मैंने zig से दूर रहने का फैसला किया। अब लगता है मेरी instinct सही थी। वजह अलग हो सकती है, लेकिन नतीजे में मुझे C++11 से पहले वाली verbosity जैसी complexity महसूस होती है। यह वही जाना-पहचाना pattern है जहाँ नई भाषा किसी पुरानी भाषा की जगह लेने आती है, और आख़िर में खुद उतनी ही complex हो जाती है
OP की यह बात कि
Stream.Readerकोstd.Io.Readerमें बदलने के लिएinterface()method कॉल करनी पड़ती है, लेकिनStream.Writerसेstd.io.Writerपाने के लिए&interfacefield का address चाहिए, यह consistency की कमी है — मुझे लगता है Go community इसे सीधा ठुकरा देती। Go में छोटे बदलाव भी बेहद गहरी analysis के बाद तय किए जाते हैं। मेरा पसंदीदा Go issue उदाहरण: Go github issue #45624। चार साल तक चर्चा करके नतीजा निकालते हैं। यह धीमा लग सकता है, लेकिन इसमें consistency, design पर गहरी सोच, और वास्तविक code usage सबका ध्यान रखा जाता है। धीमा है, लेकिन मुझे लगता है उतनी ही गति ज़रूरी है। और इस तरह लिए गए फ़ैसले आम तौर पर बहुत high quality के होते हैंRust में भी यही बात है। nightly rust में कई उपयोगी features होते हैं जो stable में नहीं आते (जैसे generator)। यह निराशाजनक हो सकता है, लेकिन stable में जो भी जाता है, वह बहुत गहराई से परखा हुआ होता है। मैं अधीर हूँ, फिर भी मुझे Rust team का तरीका सही लगता है
Go 1.0 से पहले चीज़ें इतनी धीमी नहीं थीं। शायद उतनी बुनियादी नहीं, लेकिन बड़े बदलाव काफ़ी होते थे (semicolon हटाना, error type बदलना आदि), और automatic migration tools भी दिए जाते थे। 1.0 के बाद stability का वादा किया गया, और तभी से आज वाला तरीका बना
low-level काम के लिए मेरे दिमाग़ में सबसे पहले Zig ही आता है। Zig को C/C++ cross compiler की तरह भी इस्तेमाल किया जा सकता है, और यह बहुत शानदार है
ज़्यादातर issues बस documentation की कमी या उसकी खराब गुणवत्ता से पैदा हुए लगते हैं
output.write("hello")जैसा सरल दिखाना चाहता है, लेकिन असल में उपयोग की व्याख्या कम होने से भ्रम पैदा करता है। यह भी सवाल है कि standard library को इतनी जटिल type system अभिव्यक्त करने की ज़रूरत ही क्यों है। Zig का बाकी हिस्सा साफ़, संक्षिप्त और पढ़ने में आसान methods से भरा है, लेकिन नया IO system उससे काफ़ी अलग और गैर-स्वाभाविक लगता है(zig की नई system) असल समस्या यह है कि जो अवधारणा सिर्फ़ execution boundaries बाँटने के लिए थी, उसे पूरे runtime engine में मिला दिया गया, लेकिन इन दोनों सिरों को कैसे जोड़ा जाए, यह साफ़ तौर पर बताया ही नहीं गया