زیگ چه احساسی داشت که از روست آمده بود
آدرس مقاله: https://besok.github.io/posts/what-zig-felt-like-coming-from-rust/ آدرس نظرات: https://news.ycombinator.com/item?id=49766637 امتیاز: 200 # نظر: 241
من 7 سال گذشته را بهعنوان توسعهدهنده Rust گذراندهام و بیشتر روی پروژههای منبع باز کار کردهام، و میخواهم فکر کنم که در طول مسیر احساس خوبی برای زبان و اکوسیستم آن ایجاد کردهام. من به سمت جنبه عملکردی Rust مانند توابع تمیز، انواع رسا، از این قبیل چیزها می کشم. اما من همیشه در مورد زبان های دیگر کنجکاو هستم، و Zig برای مدتی به عنوان جانشین C در رادار من بوده است: سطح پایین تر، سبک تر، و به طور پیوسته جایگاه خود را در میان زبان هایی که مردم جدی می گیرند به دست می آورد.
من در اوایل کارم زمانی را با C گذراندم، بنابراین مقایسه همیشه جالب بود.
یک اخطار که باید از قبل بیان شود: تجربه من با Zig با این پروژه آغاز می شود. برخی از مشاهدات برای افرادی که به طور روزانه با زیگ کار می کنند ساده و بدیهی به نظر می رسند و برخی از تصمیماتی که من در این مسیر گرفتم تقریبا مطمئنا تصمیمات بهینه نبودند، آنها بیشتر بر اساس عادت های منتقل شده از Rust شکل گرفته اند تا اصطلاحات عمیق Zig. این خوب است، همه باید از جایی شروع کنند، و در این بین، من بر هر شهودی که در طول سال ها ایجاد کرده ام، برای بهتر یا بد، تکیه می کنم.
برای مقایسه منصفانه، تصمیم گرفتم چیزی را که قبلا در Rust ساخته بودم، دوباره اجرا کنم، نه یک اسباب بازی، اما نه یک پروژه گسترده، و در حالت ایده آل چیزی که جامعه واقعا می تواند از آن استفاده کند. من روی JSONPath مستقر شدم: یک زبان پرس و جو برای JSON، مشخص شده در RFC 9535. نسخه Rust از قبل وجود داشت (jsonpath-rust)، و هدف این بود که همان چیزی را به Zig بیاورم: zig-jsonpath.
اولین چیزی که مرا غافلگیر کرد - و صادقانه بگویم، چه کسی انتظار داشت این بخش به یاد ماندنی باشد - پشتیبانی از IDE یا فقدان تقریبا کامل آن بود. من از RustRover برای Rust و طعمهای مختلف JetBrains برای زبانهای دیگر استفاده میکردم، و Zig، در مقایسه، چیزی فراتر از برجستهسازی نحو و تکمیل خودکار اولیه ارائه میکرد. این دقیقا تعجب آور نبود، اما من را مجبور کرد به اصول اولیه بازگردم: یادگیری کار با زبان عمدتا از طریق خط فرمان. چیزی که به عنوان یک نقطه ضعف شروع شد به یکی از جالبترین بخشهای تجربه تبدیل شد.
به نظر می رسد من به سادگی فراموش کرده ام که تکیه بر ابزار CLI ساده چقدر می تواند ساده باشد.
اولین درس واقعی در اینجا build.zig بود که با سهولت شگفتانگیزی به این موضوع رسیدگی میکند. من در نهایت به این تنظیمات رضایت دادم:
هنگامی که شرایط را پذیرفتید، کار کردن با آن واقعا تازه است.
من باید از Zig تشکر کنم، در یک راه دور، برای شروع یک واکنش زنجیره ای بزرگتر، یعنی حرکت من از یک IDE کامل به سمت یک راه اندازی مارپیچ + alacritty + zellij.
با Rust، و بسیاری از زبانهای دیگر، من همیشه زمان زیادی را صرف کردهام (به عقب و جلو رفتن) برای یافتن تعادل مناسب بین اندازه فایل و عمق پوشه. شما آزاد هستید که فایل ها را تکه تکه کنید و سلسله مراتب پوشه را تا جایی که دوست دارید عمیق کنید. مشخص شد که Zig با این نیز خوب است، اما به نوعی واقعا آن را تشویق نمی کند (مانند C، که برای یک زبان سیستمی سطح پایین تعجب آور نیست).
اگر بخواهید میتوانید فایلها و پوشهها را تودرتو قرار دهید، اما انجام این کار کمی اصطکاک وارد میکند، و سوال اصلی این است: چرا زحمت بکشید؟ در واقع با تقسیم همه چیز در فایل ها و پوشه های بیشتر، چه چیزی در خوانایی به دست می آورید؟ در تئوری، خوانایی بهتر. در عمل، وقتی چیزهای مرتبط را در یک فایل بزرگتر جمع میکنید، فقط میتوانید آن را برش دهید و به جای آن بخش به بخش پیمایش کنید و داشتن همه چیز در یک مکان یک مزیت واقعی دارد. بیشتر، Zig شما را به سمت صاف سوق می دهد. اگر چیزی برای یک مدل نیاز به همراه داشته باشد، من فقط یک فایل model_<companion> در کنار آن ایجاد می کنم و ادامه می دهم.
من فکر نمیکنم این مقیاس به پروژههای بزرگ باشد، به این معنی که در برخی مواقع شما به یک سلسله مراتب واقعی نیاز دارید، اما آستانه نیاز به آن در Zig بسیار بالاتر از آن چیزی است که انتظار داشتم. در Rust، تقریبا به صورت پیشفرض، تمایل دارم به ساختار پوشهها زودتر دست پیدا کنم. در زیگ مدام آن را به تعویق انداختم و تا پایان این پروژه اصلا به آن نیاز نداشتم.
این تضاد فراتر از Zig مفید بود، زیرا باعث شد حتی در زبانهای دیگر درباره اینکه آیا فایلها را سازماندهی میکنم چون واقعا پروژه به آن نیاز دارد یا از روی عادت، تجدید نظر کنم. همچنین این یک روش کاملا صادقانه برای سنجش میزان بزرگی یک پروژه است: اگر نمیتوانید در روز اول از دستیابی به پوشهها مقاومت کنید، شاید کوچکتر از آن چیزی باشد که به نظر میرسد.
فعلا مجموعه سازگاری rfc9535 را کنار بگذارید و صرفا روی خود زبان تمرکز کنید:
در Rust، من تمایل دارم از دو رویکرد برای آزمایش استفاده کنم:
من تقریبا همان جدایی را از زیگ انتظار داشتم. روی کاغذ، شبیه به هم به نظر می رسد: می توانید آزمایش ها را مستقیما در همان فایل بنویسید. مشکل، حداقل برای من، پرحرفی بود. با توجه به ساختار مسطحی که قبلا در آن مستقر شده بودم، دو گزینه برای من باقی ماند، یا یک فایل model_test جداگانه برای هر مدل، یا آزمایش هایی که مستقیما در خود فایل مدل قرار داده شده اند. هر دو روش در نهایت به چیزهایی درهم ریخته شدند: یا فایل های فردی یا پوشه اصلی به عنوان یک کل.
من با گزینه دوم رفتم که به معنای پیکربندی صریح آن در build.zig بود. با این حال، هنگامی که سیم کشی شد، به خوبی کار کرد و تمیز ماند.
بنابراین به طور کلی: نوشتن و مدیریت تست ها در Rust برای من آسان تر است. اما در مورد Zig، بیشتر این اصطکاک اضافی مربوط به زبان است، به جای آزمایش زیرساخت، به مدیریت حافظه دستی Zig مربوط می شود.
Rust از نظر فنی یک زبان ضروری است، اما به شدت از مفاهیم کاربردی استفاده می کند: تکرار کننده های هزینه صفر، ارزیابی تنبل، ADT ها، تطبیق الگو، انواع مونادیک، ویژگی ها، بسته شدن و غیره. با گذراندن زمان با Haskell و Erlang، من نسبتا به سبک عملکردی تمایل پیدا کردم و در این کتابخانه نشان داده می شود. به شدت به اصطلاحات FP متکی است:
میدانستم که نمیتوانم همه اینها را به Zig بیاورم، اما امیدوارم بتوانم حداقل مفاهیم اصلی را حفظ کنم. در عمل، جایی که Rust به تغییر ناپذیری و ترکیبکنندهها تکیه میکند، زیگ مرا به سمت جهش در محل و بومیترین الگوی دنیای ضروری سوق داد.
اما زبان به سرعت شما را مجبور میکند از سبک عملکردی فاصله بگیرید، بیشتر به این دلیل که اکنون مستقیما با تخصیصدهندهها سر و کار دارید و یک رویکرد کاربردی واقعا خالص به معنای ساختن مداوم ساختارهای جدید است. این یا در حافظه گران است یا در حسابداری دستی که برای جلوگیری از آن گران است.
جهش در مقابل موناد تغییرناپذیر تفاوت اصلی است.
Rust یک تحول مونادیک مستقیم انجام می دهد:
در مجموع، این منعکس کننده اهداف طراحی و دامنه هدف هر زبان است، و این یک مبادله منطقی است، اما به طور ذهنی، من دریافتم که کد Zig حاصل کمتر از همتای Rust آن قابل خواندن است.
تخصیص دهنده ها همه جا هستند. تقریبا هر تابع یکی را می پذیرد. هر ساختاری یکی را نگه می دارد. صریح است، و هنگامی که شما آن را به عنوان هزینه ورود بپذیرید، پیگیری آن نسبتا ساده است. این کم و بیش ویژگی تعیین کننده زبان است، بنابراین نمی توانم بگویم به من هشدار داده نشده است.
با این حال، در عمل، این روند خسته کننده است. شما باید به طور دقیق از قرارداد init/deinit پیروی کنید، و این نظم زمانی که پشته تماس شما طولانی می شود، متزلزل می شود. این یک پیشرفت واضح نسبت به یک خطای Segfault خاموش یا حافظه خراب در C است، اما از Rust، هنوز هم شما هستید که قانون را با دست اجرا میکنید: چیزی را اختصاص دهید، مسیر شکست را مدیریت کنید، تصمیم بگیرید که چه کسی مسئول deinit است، هر بار.
خوشبختانه، Zig's TestAllocator در اینجا به کمک می آید. همه چیز را بهطور خودکار دریافت نمیکند، شما هنوز باید موارد آزمایشی را بنویسید که مسیرهای شکست را اعمال میکنند - اما وقتی این کار را انجام دادید، نسبتا قابل اعتماد است. و این تله است: همه اینها روی کاغذ واضح به نظر می رسد، درست تا زمانی که کد پیچیده تر شود، در این مرحله این اشکالات خود را در هم می بندند و پنهان می کنند.
در اینجا مواردی هستند که بیشترین ضربه را میزنند، که هر کدام با نحوه برخورد Rust با همان شکل مقایسه شدهاند:
دستگیر شده توسط: MemoryLeakDetected، با اشاره به تماس فریبنده داخل ضمیمه.
Rust: Drop به طور خودکار در انتهای محدوده اجرا می شود، بنابراین این باگ خاص به سادگی وجود ندارد. اگرچه از نظر فنی، نشت در Rust مانند چرخههای مرجع Rc یا یک Box::نشت صریح همچنان امکانپذیر است، بنابراین «هرگز نشت» تضمین سختی نیست، فقط چیزی است که باید برای راهاندازی آن تلاش کنید.
توسط: FailingAllocator{.fail_index = 1}، که ضمیمه دوم را وارد MemoryLeakDetected میکند.
رفع: errdefer iter.deinit(); درست بعد از شروع
زنگ زدگی: واقعا از بین رفته است. Drop::drop بدون قید و شرط در هر خروجی از محدوده، از جمله بازگشت زودهنگام از ? .
توسط : در حال اجرا تحت std.testing.allocator، که در cache.items[0] query دوم ناموفق بود.deinit() در حین پاکسازی processAll، DoubleFree به هر دو سایت رایگان اشاره میکند و تأیید میکند که این یک اشکال مالکیت متقابل است، نه یک اشتباه تایپی تک خطی.
رفع: فقط یک لایه ممکن است دارای مقدار باشد. از آنجایی که حافظه نهان بیشتر از cacheAndLog عمر می کند، مالکیت متعلق به لایه سه است. لایه دو نباید پس از واگذاری آن را به تعویق بیندازد:
Rust: این شکل دقیق نمی تواند کامپایل شود. cache.push(result) نتیجه را جابهجا میکند - پس از آن خط، نتیجه دیگر به عنوان یک اتصال قابل استفاده وجود ندارد، بنابراین راهی برای تماس تصادفی drop (نتیجه) وجود ندارد.
توسط : FailingAllocator{ .fail_index = 1} شکست در رشد آرایه (تخصیص دوم)، یتیم شدن فریب خورده (تخصیص اول).
این به روشی متفاوت از مورد اول نشت می کند: iter.deinit() به خوبی اجرا می شود، فقط هرگز این رشته خاص را نمی بیند.
زنگ زدگی: در ساخت و ساز درست است. Vec::push(item) مورد را وارد میکند و یا موفق میشود یا در OOM سقط میشود و هیچ فشار خطاپذیری در API استاندارد وجود ندارد که یک مقدار «تخصیصیافته اما بدون پیوند» را به شما بازگرداند تا به طور تصادفی از دست بدهید. شکافی که errdefer در اینجا پر می کند، به سادگی وجود ندارد.
اکوسیستم هنوز خیلی جوان است. کمبود واقعی کتابخانهها وجود دارد، و حتی چیزی بهاندازه regex کاملا بالغ نیست، بهعنوان مثال mvzr، موتور regex موجود در Zig، از فرارهای ویژگی یونیکد (\p{...}) پشتیبانی نمیکند، که مستقیما بهعنوان یک شکاف هنگام اجرای توابع فیلتر RFC 9535 ظاهر شد. علاوه بر این، کتابخانه استاندارد خود زبان API خود را از نسخه به نسخه تغییر می دهد. هیچکدام از اینها تعجبآور نبود، اما برای ثبت ارزش آن را دارد.
زبان با Rust متفاوت است (کسی که می توانست چنین فکر کند، بله)، اما واقعا تأثیر خوبی بر جای گذاشت. این ساده، مدرن و فوق العاده سریع است. من معتقدم که این زبان پتانسیل واقعی برای تبدیل شدن به جانشین واقعی C را دارد. از سوی دیگر، هنوز جوان است، و این نشان میدهد: شکل خود زبان در جاهایی ناتمام به نظر میرسد، و من گمان میکنم که ویژگیهای کیفیت زندگی خنکتر و قند نحوی با بلوغ آن بیشتر شود.
در مورد من، من می خواهم به کمک به اکوسیستم ادامه دهم، و هر زمان که با پروژه ای که ارزش ساختن داشته باشد، این کار را خواهم کرد.
سلب مسئولیت: استایل و مدیریت خطا در سراسر این مقاله با کمک هوش مصنوعی پاکسازی شد.
متن اصلی (انگلیسی)
What Zig felt like, coming from Rust
Article URL: https://besok.github.io/posts/what-zig-felt-like-coming-from-rust/ Comments URL: https://news.ycombinator.com/item?id=49766637 Points: 200 # Comments: 241