Хабрахабр

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

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

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

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

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

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

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 означает, что ссылка является действительной на протяжении всего времени исполнения программы. Но на деле синтаксис объявления лайфтайма довольно прост — это всего-лишь идентификатор, за которым следует апостроф.

Если ссылка действительна, то она поддаётся разыменованию без паники, ошибки сегментации и прочих прелестей. Что такое "действительность ссылки"? все автоматические переменные функции produce_something() очищаются после её вызова: Например, в функции main() указатель 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() ; // Владение (owning) let y = &x; // Заимствование (borrowing)
}

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

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

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

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

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

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

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

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

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

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

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

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



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

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

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

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть