Опровергаем четыре стереотипа о языке программирования Rust.

Язык программирования RUST, начатый как хобби-проект, а впоследствии поддерживаемый корпорацией Mozilla, позволяет дефолтным программистам писать одновременно и безопасные и быстрые системы: от калькуляторов до высоконагруженных серверов.

За своё относительно короткое время существования, данный язык уже успел обрасти стереотипами, четыре из которых я попытаюсь опровергнуть ниже.

  1. Rust — сложный язык программирования
  2. Rust — ещё один «убийца C/C++»
  3. Unsafe губит все гарантии, предоставляемые Rust
  4. Rust никогда не обгонит C/C++ по скорости

1. Rust — сложный язык программирования

Сложность языка программирования обуславливается наличием большого числа несовместимых между собой синтаксических конструкций. Яркий пример — C++ и C#, ведь для абсолютного большинства программистов C++ является сложнейшим языком, коим не является C#, несмотря на большое количество синтаксических элементов. Rust довольно однороден, т.к. изначально был спроектирован с оглядкой на ошибки прошлого, а все нововведения вводятся исключительно при условии согласования с уже имеющимися.

Данный стереотип восходит своими корнями к концепции времён жизни ссылок, позволяющей на уровне семантики языка описывать гарантии действительности используемых ссылок. Синтаксис лайфтаймов выглядит сперва странным:

struct R<'a>(&'a i32);
unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> {
    std::mem::transmute::<R<'b>, R<'static>>(r)
}

unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>)
                                             -> &'b mut R<'c> {
    std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r)
}

 

Но на деле синтаксис объявления лайфтайма довольно банален — это всего лишь идентификатор, перед которым следует апостроф. Лайфтайм 'static означает, что ссылка является действительной на протяжении всего времени исполнения программы.

Что такое «действительность ссылки»? Если ссылка действительна, то она поддаётся разыменованию без паники, ошибки сегментации и прочих прелестей. Например, в функции main() указатель something становится недействительным, т.к. все автоматические переменные функции produce_something() очищаются после её вызова:

int *produce_something(void) {
    int something = 483;
    return &something;
}

int main(void) {
    int *something = produce_something();
    int dereferenced = *something; // Segmentation fault (core dumped)
}

Семантически лайфтайм может быть задан посредством других лайфтаймов. Например, эта функция требует того, чтобы ссылка foo не стала недействительной до недействительности ссылки bar:

fn sum<'a, 'b: 'a>(foo: &'b i32, bar: &'a i32) -> i32 {
    return foo + bar;
}

Конструкция выше применяется крайне редко, чаще всего одного лишь указания лайфтайма более чем достаточно. Лайфтаймы — это элегантная абстракция, дающая возможность программистам быть уверенными в действительности собственных ссылок как в контексте однопоточных, так и многопоточных систем. Сложность лайфтаймов сильно переоценена.

Изучающих Rust также пугают концепции заимствования и владения (которые существовали и до создания Rust). Как и с лайфтаймами, концепции заимствования и владения лаконично вписываются в общую картину маслом : заимствование — это взятие ссылки на значение, а владение — это связь идентификатора переменной с её значением.

Рассмотрим на примере. В приведённом ниже коде переменная x владеет экземпляром структуры Foo, а переменная y заимствует значение, которым владеет переменная x:

 

struct Foo {
    data: Vec<u8>,
}

fn main() {
    let x = Foo { data: Vec::new() }; // Владение (owning)
    let y = &x; // Заимствование (borrowing)
}

Если один поток имеет иммутабельную ссылку на переменную, а другой поток имеет мутабельную, то второй может изменить значение, вызвав состояние гонки. Данная ситуация недопустима в безопасном языке, вследствие чего команда Rust определила два простых правила:

  • Значение может быть заимствовано иммутабельными переменными множество раз и при этом не заимствовано мутабельной;
  • Значение может быть заимствовано мутабельной переменной лишь один раз и при этом не быть заимствованным иммутабельными.

2. Rust — ещё один «убийца C/C++»

 

Ключевая фраза — «ещё один». На данный момент Rust — единственный язык программирования, обладающий одновременно активным сообществом и характеристиками, позволяющими ему решать задачи, решаемые языками C/C++. Синтаксис и семантика позволяют с лёгкостью изъясняться на разных уровнях абстракции — от инструкций SIMD до управления веб-серверами.

Данный стереотип возник вследствие языков Vala, Zig, Golang и подобных. Как я сказал выше, у этих языков либо слишком маленькое сообщество, либо они теоретически и практически не смогут работать на всех системах, на которых способны работать C/C++. У Vala и Zig маленькое сообщество, а Golang берёт курс на вытеснение интерпретируемых языков и не может работать на системах с критической нехваткой ресурсов, т.к. поставляется с дополнительной средой выполнения (например, сборщик мусора).

Очевидно, что языки C/C++ будут жить ещё очень много лет из-за накопленного за десятилетия кода и программистов, пишущих на них, но Rust имеет все шансы потеснить их, как это когда-то сделала Java.

3. Unsafe губит все гарантии, предоставляемые Rust

Unsafe — это конструкция языка, позволяющая совершать операции, способные привести к неопределённому поведению (UB). В действительности, unsafe позволяет делать лишь четыре операции, запрещённые в «безопасном» Rust:

  • Вызов небезопасной функции;
  • Реализация небезопасного трейта;
  • Разыменование глобальной статической мутабельной переменной;
  • Разыменование сырого указателя.

Во-вторых, потенциально небезопасный блок кода может быть инкапсулирован в безопасный блок, т.к. компилятор предполагает, что программист не сомневается в его правильности. Суть потенциально небезопасного блока кода в том, что при его неправильном использовании поведение программы не однозначно.

 

fn safe_display() {
    unsafe {
        let x = 385;
        let x_ref: *const i32 = &x;
        println!("{}", *x_ref);
    }
}

 

Функция safe_display() полностью безопасна, т.к. правильность потенциально небезопасного блока формально доказуема. Пользователь может использовать эту функцию без боязни UB.

Данный стереотип можно встретить в несколько иной трактовке: «Rust станет популярным лишь тогда, когда его сделают полностью небезопасным». И снова неверно, т.к. не все гарантии, предоставляемые Rust, могут работать в полностью небезопасном коде. Концепция Rust теряется при отсутствии гарантий безопасности.

4. Rust никогда не обгонит C/C++ по скорости

Утверждение безосновательное. В теории, программа, написанная на языке Rust, может быть оптимизирована столь же хорошо, как и аналогичная программа на C/C++. В некоторых синтетических тестах производительности Rust даже обгоняет GCC C:

 



 

Что касается тестов производительности на реальных задачах, то можно отметить замеры производительности RapidJSON и serde_json. serde_json парсит DOM медленнее, чем это делает RapidJSON, но при сериализации / десериализации структур serde_json обогнал RapidJSON (DOM) как на GCC, так и на CLANG:

 

 

Также можно отметить библиотеку Rustls, обогнавшую знаменитую OpenSSL практически во всех тестах (на 10% быстрее при установке соединения на сервере и на 20%-40% быстрее на клиенте, на 10%-20% быстрее при восстановлении соединения на сервере и на 30%-70% быстрее на клиенте).

По сути, когда мы сравниваем производительность Rust, скомпилированного стандартным компилятором rustc, с производительностью C/C++ кода, скомпилированного посредством CLANG, то мы сравниваем качество сгенерированного LLVM IR. В один случаях он может быть более подвержен оптимизациям, в других — менее.

Заключение

Rust не обделён недостатками, таких как сложности реализации некоторых структур данных, временная вялость экосистемы (которая с каждым годом всё нивелируется) и так далее. Надеюсь, что язык займёт свою нишу в современном программировании.

Вам также может понравиться

About the Author: admin

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *