Някои причини, поради които определено трябва да опитате Rust.

Наскоро прочетох статия, критикуваща Rust, и въпреки че имаше куп добри точки, не бях съгласен с нея - това е аргументирана статия. Като цяло не бих препоръчал статия, която критикува Rust. Жалко – важно е да се изправим лице в лице с недостатъците, но и да противодействаме на критиките, които са небрежни или неточни.

И така, ето някои от моите причини да подкрепям Rust.

Не всички разработки са системно програмиране

Rust е език за системно програмиране. Той осигурява прецизен контрол върху оформлението на данните и поведението на кода по време на изпълнение, като ви дава максимална производителност и гъвкавост. За разлика от други езици за системно програмиране, той също така осигурява безопасност на паметта - програма с грешки прекратява по добре дефиниран начин, вместо да проявява (потенциално застрашаващо сигурността) недефинирано поведение.

В повечето случаи обаче човек не се нуждае от максимална производителност или екстремен контрол върху хардуерните ресурси. В този случай модерен управляем език като Kotlin или Go предлага прилична скорост и завидна производителност с гарантирана безопасност на паметта благодарение на динамичното управление на паметта с събирач на отпадъци.

Сложността

Времето на програмиста е ценно и ако изберете Rust, очаквайте да прекарате част от това време в изучаване на различни техники за използване. Общността на Rust отделя много време за създаване на различни висококачествени уроци, но езикът на Rust е огромен. Дори ако Rust може да ви предостави стойност, може да нямате много енергия, която да инвестирате в подобряване на езиковата си експертиза.

Цената на увеличения контрол на Rust е омагьосването на избора.

struct Foo     { bar: Bar         } 
struct Foo<'a> { bar: &'a Bar     } 
struct Foo<'a> { bar: &'a mut Bar } 
struct Foo     { bar: Box<Bar>    } 
struct Foo     { bar: Rc<Bar>     } 
struct Foo     { bar: Arc<Bar>    }

В Kotlin започвате клас Foo(val bar: Bar) и преминавате към решаване на вашия бизнес проблем. В Rust трябва да направите някои избори, някои достатъчно важни, за да изискват специализиран синтаксис.

Цялата тази сложност съществува по някаква причина — ние не знаем как да създадем по-прост език на ниско ниво, безопасен за паметта, въпреки че не всяка задача трябва да бъде решена на език на ниско ниво.

Време за компилиране

Времената за компилиране са кратни на цялата работа. Кодът, написан на език за програмиране, който работи по-бавно, но се компилира по-бързо, има възможност да работи по-бързо, защото програмистът има повече време да оптимизира кода.

Rust умишлено е избрал бавни компилатори в главоблъсканицата на общото. Това не е непременно краят на света (тъй като произтичащите от това подобрения в производителността по време на работа са реални), но означава, че в по-големи проекти ще трябва да се борите за разумни времена за изграждане.

rustc прилага това, което е може би най-напредналият алгоритъм за инкрементална компилация сред производствените компилатори, но това изглежда малко като борба с езиковия модел на компилация. (https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation .html)

За разлика от C++, компилациите на Rust не са неудобно паралелизирани, като степента на паралелизъм е ограничена от дължината на критичните пътища в графиката на зависимостите. Това ще се покаже, ако компилирате 40+ ядра.

Rust също така няма функция, подобна на пъпка, което означава, че промяната на кутия изисква повторно компилиране (не само повторно свързване) на всички негови обратни зависимости.

Ниво на развитие

Само на 5 години Rust определено е млад език. Въпреки обещанието му, направих много повече залози на „C ще съществува след десет години“, отколкото на „Rust ще съществува след десет години“ (вижте Ефекта на Линди). Ако пишете софтуер, който ще продължи десетилетия, трябва сериозно да обмислите рисковете, свързани с избора на нова технология. Но имайте предвид, че изборът на Java пред Cobol за банков софтуер през 90-те години се оказа правилният избор в ретроспекция). (Lindy Effect: https://en.wikipedia.org/wiki/Lindy_effect)

В момента Rust има само една пълна реализация – компилаторът rustc. Друга най-добра алтернативна реализация, mrustc, умишлено пропуска много статични проверки за безопасност. rustc в момента поддържа само един готов за производство бекенд — LLVM. Като такъв, той има по-тясна поддръжка за CPU архитектури от C, който има реализация на GCC, както и много собствени компилатори, специфични за производителя.

И накрая, на Rust липсва официална спецификация. Референтната документация е в процес на работа и не е документирала всички по-фини детайли на изпълнението.

Заменяемост

В света на системното програмиране, освен Rust, има няколко други езика, главно C, C++ и Ada.

Съвременният C++ предоставя инструменти и насоки за подобряване на безопасността. Има дори предложения за подобен на Rust механизъм на жизнения цикъл. За разлика от Rust, използването на тези инструменти не е гарантирано без проблеми с безопасността на паметта. Въпреки това, ако вече поддържате голямо количество C++ код, има смисъл да проверите дали спазването на най-добрите практики и използването на дезинфектант са полезни за решаване на проблеми със сигурността. Трудно е, но очевидно е по-лесно, отколкото да го пренапишете на друг език.

Ако използвате C, можете да използвате формални методи, за да докажете, че няма недефинирано поведение, в противен случай можете само да тествате всичко изчерпателно.

Ada е безопасна за паметта, ако не използвате динамична памет (никога не се обаждайте безплатно).

Ръждата е интересна точка от кривата цена/безопасност, но със сигурност не е единствената.

Инструмент

Инструментите за ръжда са нещо, което заслужава възхищение. Базови инструменти, компилатори и система за изграждане (карго), често считани за първокласни.

Но, например, някои инструменти, свързани с времето за изпълнение (най-вече анализ на купчина), в момента не съществуват - без инструменти за изпълнение е много трудно да се анализира времето за изпълнение на програмата. Освен това, въпреки че поддръжката на IDE е прилична, тя не е близо до надеждността на ниво Java. Автоматично комплексно преработване на многомилионни програми не е възможно в Rust днес.

производителност

„Използването на LLVM“ не е универсално решение за всички проблеми с производителността. Въпреки че не знам бенчмарк за производителността на C++ и Rust в мащаб, не е трудно да се измисли списък с някои проблеми с производителността, при които Rust не е толкова добър, колкото C++.

Най-голямата вероятно е, че семантиката на движение на Rust е базирана на стойност (memcpy на ниво машинен код). За разлика от тях семантиката на C++ използва специални препратки (указатели на ниво машинен код), където можете да работите с данни. На теория компилаторите трябва да могат да виждат през вериги за копиране, но на практика често не могат. #57077. Свързан проблем не е поставянето на нови — Rust понякога трябва да копира байтове от стека, докато C++ може да конструира обекти на място. (https://github.com/rust-lang/rust/issues/57077)

Донякъде интересно, ABI по подразбиране на Rust (който не е стабилен, за да бъде възможно най-ефективен) понякога е по-лош от C. (https://github.com/rust-lang/rust/issues/26494#issuecomment-619506345)

И накрая, докато на теория кодът на Rust трябва да бъде по-ефективен поради по-богата информация за псевдоними, активирането на оптимизации, свързани с псевдоними, може да доведе до LLVM грешки и неправилно компилиране. (https://github.com/rust-lang/rust/issues/54878)

Но, да повторя, това са единични примери, а понякога в тези области е точно обратното. Например, проблемите с производителността на std::unique_ptr не съществуват в Rust’s Box. Потенциално по-голям проблем е, че дефинициите на Rust, проверени във времето, не са толкова изразителни като C++. Следователно някои високопроизводителни C++ шаблонни трикове са трудни за изразяване в красив синтаксис в Rust.

Определение за опасно

Може би по-централен проблем от собствеността и заемането са опасните граници. Чрез ограничаване на всички опасни операции зад опасни блокове и функции и предоставянето им на безопасни интерфейси от по-високо ниво е възможно да се създаде функция, която е и двете:

Разумно (кодът, който не е опасен, не може да причини недефинирано поведение).
и модулност (различни опасни блокове могат да се проверяват поотделно).
Очевидно това обещание е потвърдено на практика: кодът на Rust с грешки ще изпадне в паника, а не препълване на буфера.

Но на теория проблемът не е толкова оптимистичен.

Първо, няма дефиниция на модела на паметта на Rust, така че няма начин официално да се провери дали даден опасен блок е валиден. Има неофициални дефиниции за това „от какво прави или може да зависи rust-c“ и се изпълняват валидатори по време на изпълнение, но действителният модел се променя. Така че може да има опасен код, който все още може да се използва на практика днес, може да бъде обявен за невалиден утре и да бъде повреден от нови оптимизации на компилатора през следващата година.

Второ, и още едно наблюдение, небезопасните блокове всъщност не са модулни. Достатъчно силните опасни блокове всъщност могат да разширят езика. Две такива разширения може да са добри, когато се използват самостоятелно, но могат да доведат до недефинирано поведение, наблюдавана еквивалентност и опасен код, ако се използват заедно. (https://smallcultfollowing.com/babysteps/blog/2016/10/02/observational-equivalence-and-unsafe-code/)