На сегодняшний день существует уйма языков программирования. Одни из них старые и заслуженные, такие как C/C++, другие — просто модные. Но есть и такие, которые, помимо моды, новизны и популярности, еще и весьма перспективные. И сегодня мы поговорим об одном таком языке. Это язык Rust (неофициально именуемый «Ржавым»), который по состоянию на 2021 год был самым высокооплачиваемым языком в мире.
Итак, сегодня мы подробно поговорим о том, какие особенности имеет этот язык, где и в каких задачах применяется, какими преимуществами и недостатками обладает. Поехали!
Содержание:
1. Что такое Rust?
2. Особенности языка Rust
3. История языка программирования Rust
4. Преимущества и недостатки языка Rust
5. Основы синтаксиса Rust
5.1. Первая программа
5.2. Переменные в Rust
5.3. Типы данных
5.4. Функции
5.5. Комментарии
5.6. Условные инструкции
5.7. Владение
5.8. Структуры
6. Сферы применения
Итоги
Язык программирования Rust — это универсальный ЯП, который разрабатывают в компании Mozilla. Он основан на трех «китах» — скорости, безопасности и эргономике. Этот язык весьма молод — он релизнулся в 2015 году (первая версия вышла 15 мая), но при этом активно развивается. Авторы позиционируют его в качестве одного из вероятных наследников C/C++. И тому есть немало причин.
Несмотря на новизну языка, каждые 6 недель выходит новый релиз, добавляющий новые возможности. Разработка идет очень активно, права на язык принадлежат Rust Foundation, который учредили компании AWS, Huawei, Google, Microsoft и Mozilla. Отметим, что в период с 2016 по 2020 год Rust занимал первое место в списке любимых языков по итогам ежегодного опроса разработчиков Stack Overflow Developer Survey.
Язык программирования Rust изначально разрабатывался как быстрый и лаконичный. Потому первой его особенностью является синтаксис, который похож на синтаксис C. Программы на этом языке также выполняются быстро.
Еще одна особенность — это высокий уровень защищенности данных в памяти. Язык самостоятельно управляет размещением данных в памяти, используя указатели. Это позволяет избежать многих ошибок, связанных с переполнением стека. Для этого задействована модель управления памятью на основе безопасных шаблонов параллельного выполнения.
Помимо этого, язык обладает мощным анализатором кода, который предотвращает утечки памяти и предупреждает ошибки при работе с многопоточностью. Таким образом, программы на Rust не только быстрые, но также и защищенные.
В языке также нет garbage collection или сборщика мусора, который присутствует во многих других языках. Эта технология автоматически очищает память от ненужных уже объектов. Но в Rust, как сказано выше, используется система указателей, так что сборка мусора попросту не нужна.
ЯП Rust используется не только для прикладного, но и для системного программирования. Подробнее о том, где и как он применятся, мы поговорим дальше, а пока отметим, что на этом языке можно писать даже ядро операционной системы. На данный момент есть проект перевода ядра Linux на Rust. Помимо этого, в 2019 году Microsoft начала разработку своего языка на базе Rust, на который потенциально могут перевести и Windows.
Сам язык начал разрабатываться с 2006 года сотрудником Mozilla Грэйдоном Хором, проектом он занимался в свободное время. Три года спустя проект получил финансирование от компании, а в следующем, 2010 году, его представили на Mozilla Summit 2010. Первая релизная версия появилась в 2015 году.
Название автор взял от грибов семейства «ржавчинные». По словам Хора, их особенность в том, что это «распределенные организмы», которые лишены «единой точки отказа» и могут выживать в самых разных условиях. Эти грибы также умеют быстро расти и распространяться.
По мнению автора, это было хорошей аналогией для языка, который «заточен» на скорость и безопасность работы.
После того, как Хор показал свои наработки в компании, там заинтересовались проектом, поскольку хотели перейти от С++ к другим языкам и упростить технологии. После этого начались попытки использования языка в готовых решениях. К примеру, на Rust написали браузерный движок Servo, который создавали вместе специалисты Mozilla и Samsung.
В настоящее время язык используется для написания ряда проектов.
В этом разделе мы поговорим о том, какими преимуществами и недостатками обладает Rust. Как и любой другой язык, у него есть свои плюсы и минусы, о которых мы и поговорим в данном разделе.
Таким образом, главными плюсами Rust можно счесть скорость и безопасность, а минусы вытекают из них — жесткие требования к структуре кода, необычные решения и высокий порог вхождения.
Здесь мы разберем все основные примеры синтаксических конструкций языка. И начнем с традиционного «Hello World!
» на Rust.
Первая реальная программа на этом языке выглядит так:
fn main() { println!("Hello World!"); }
Разберем все части этого кода.
fn
— так обозначается функция (она же function).
В Rust это базовая конструкция, которая предназначена для выполнения задач. Мы объявили функцию и затем назвали ее — main ().
В скобках мы указываем параметры функции, если таковые есть. Они могут быть, а могут и отсутствовать.
В фигурных скобках или { }
мы указываем тело программы, то есть прописываем, что именно делает функция main ().
В данном случае она выводит данные на экран с помощью макроса println!.
Этот макрос похож на функцию, он добавляет новую строку и выводит данные, которые мы задали в скобках — ("Hello, world!"
). Отметим, что макрос отличается от функции именно восклицательным знаком.
Точка с запятой — это конец инструкции. В данном случае это конец макроса.
Переменные в Rust, как и в других языках, используются для хранения данных. Формат объявлений переменных таков:
let [variable_name] = [value];
Имя переменной должно быть информативным, т. е. описывать, чем является ее значение. Например:
let my_name = "Ryan";
Здесь создана переменная my_name
со значением Ryan.
Рекомендуется давать переменным названия, начинающиеся со строчной буквы, а новое слово начинать с заглавной. Это не обязательно, но считается хорошим тоном. При этом в Rust переменные не меняются по умолчанию. То есть, их значение нельзя изменить после того, как они заданы.
Например, вот этот код выдаст ошибку во время компиляции:
fn main() { let x = 5; println!("The value of x is: {}", x); x = 6; println!("The value of x is: {}", x);}
То есть, мы сначала задали значение x равное 5, а затем попытались изменить его на 6. Это неправильно.
На первый взгляд такое свойство языка Rust кажется неудобным, но оно помогает устранить баг ошибочного присваивания переменной неверного значения. Ведь такие ошибки сложно отслеживать. А безопасность, как сказано выше, это одно из основных свойств Rust.
Если же надо создать изменяемую переменную, то это делается так:
let mut x = 5;
Изменяемые переменные чаще всего используются как переменные-итераторы или как переменные в структурах цикла while.
Здесь используется команда mut,
чтобы четко определить, что переменная меняется.
В Rust можно четко указывать тип данных для переменной. Для этого используется &[type].
Если нам надо указать тип для my_name,
это делается так:
let my_name = "Ryan"; // с явно определенным типом let my_name: &str = "Ryan"; // с явно определенным типом
То есть, мы четко определили, что это будет строка, а не число.
К примеру, есть переменная answer,
которая записывает ответ пользователя в форму.
let answer = "true";
Rust неявно определяет строковый тип для этой переменной, так как она приводится в кавычках.
Но при этом название переменной может указывать на булевый тип с вариантами ответа true
и false.
Чтобы избежать непонимания и ошибок, лучше объявить переменную явно:
let answer: bool = true;
Основные типы данных на Rust:
Float:
числа с плавающей запятой (с десятичными знаками).Boolean:
логический (булев) с выбором между двумя вариантами (true
или false).
String:
строковый (набор символов, заключенных в кавычки).Char:
скалярное значение Юникод, представляющее конкретный символ.Never:
тип без значения (обозначается как !).То есть, лучше использовать явные обозначения, что укладывается в парадигму безопасного языка.
Мы уже рассмотрели базовую функцию main(),
но есть и другие. Функции часто представляют собой одну повторяющуюся задачу, которую можно вызывать в разных частях программы. К примеру, это addUser
(добавление пользователя) или changeUsername
(изменение имени пользователя).
При этом такие функции должны иметь уникальное имя и возвращать результат, а также иметь параметры. Формат выглядит так:
fn [functionName]([parameterIdentifier]: [parameterType]) { [functionBody] }
Fn
— это объявление функции.
[functionName]
— имя или идентификатор, по которому еще можно будет вызвать.
()
— те самые параметры, которые приводятся в скобках. Если их нет, оставляем скобки пустыми.
[parameterIdentifier]
— имя передаваемого значения, которое выступает в роли переменной. Его можно вызвать в любом месте в пределах функции.
[parameterType]
— тип параметра, который надо указать явно.
{}
— фигурные скобки указывают на начало и конец блока кода. Этот код выполняется при каждом вызове идентификатора функции.
[functionBody]
— здесь должен быть только код, связанный с выполнением задачи функции.
Таким образом, наш «Hello, world!»
будет выглядеть так:
fn say_hello() { println!("Hello, world!"); } fn main() { say_hello(); }
Как видите, все просто.
Комментарии в коде позволяют понять, что делает тот или иной участок кода. Компилятор не обрабатывает их, так что они нужны только для удобства программиста. Однако их желательно писать, особенно в крупных проектах, где разные люди работают над разными частями кода.
В Rust есть два способа писать комментарии. Первый — использовать двойной слэш (это по-русски называется двойной косой чертой) //.
В этом случае все до конца строки игнорируется компилятором. Например:
fn main() { // Эта строка полностью игнорируется println!("Hello, world!"); // А эта напечатала сообщение // Все готово, программа завершена }
Второй способ — начинать комментарий косой чертой со звездочкой /*
и завершать его звездочкой с косой чертой */.
Такой подход позволяет размещать комментарий в строках кода и использовать несколько строк.
fn main(/* я могу это сделать! */) { /* первый комментарий */ println!("Hello, world!" /* второй комментарий */); /* Все готово. Пока! третий комментарий */}
Также можно закомментировать те участки кода, которые сейчас не нужны, но в будущем потребуются.
Это аналоги логических операторов в других языках. То есть, они выполняются только в случае если некое условие или набор условий истинны. Все условные инструкции содержат проверяемую переменную и целевое значение, а оператор условия вида ==,
<
или >
определяет их соотношение. На выходе это дает результат true
(«истинно»)
и false
(«ложно»),
если переменная удовлетворяет или не удовлетворяет значению. В общем, обычная логическая переменная.
Существует три условных оператора: if, if else
и while.
Первый создает классическую пару вариантов — «если
— то».
Условный оператор if
выглядит так:
fn main() { let is_hot = false; { println!("It's hot!"); }}
То есть, если условие истинно, выполняется код, если же нет — пропуск этого пункта и переход на следующую позицию.
if else
— если условие истинно, выполняется тело кода A
. В противном случае выполняется тело кода B
. Не менее классическое ветвление.
fn main() { let is_hot = false; if is_hot { println!("It's hot!"); } else { println!("It's not hot!");
while:
тело кода многократно выполняется, пока условие true
(«истинно»).
Как только условие становится false
(«ложным»),
выполнение этого цикла прекращается.
while is_raining() { println!("Hey, it's raining!"); }
При этом нужно, чтобы в циклах while
проверяемая переменная была изменяемой, чтобы было условие выхода. Иначе цикл будет бесконечным.
Владение — это центральная особенность Rust и одна из причин его популярности. Речь идет о том самом механизме освобождения памяти, который есть в Rust. В тех же Java, JavaScript или Python есть сборщики мусора, которые автоматически удаляют неиспользуемые ссылки. В C или C++ от разработчиков требуется делать это вручную, что требует больше времени и создает проблемы.
В Rust же используется система владения, которая управляет памятью с помощью набора правил, применяемых компилятором во время компиляции. Для нее есть ряд правил:
Это позволяет выделять память под объявленные переменные лишь пока они используются. При передаче переменных в качестве параметров в другую функцию, они копируются туда и назначаются другому владельцу. А первый лишается их.
fn main() { let x = 5; // переменная x владеет значением 5 function(x); } fn function (number : i32) { // number становится владельцем значения 5 let s = "memory"; // начинается область видимости переменной s, здесь s становится действительной // здесь с s выполняются какие-то действия } // эта область видимости заканчивается, и теперь s больше недействительна
Главный вывод касается разного использования s
и x
. Сначала x владеет значением 5, но после выхода ее из области видимости функции main()
переменная x
должна передать владение параметру number.
Ее использование в качестве параметра позволяет продолжить область видимости выделения памяти под значение 5 за пределы исходной функции.
С другой стороны, переменная s не используется в качестве параметра и поэтому память для нее остается выделенной только тогда, когда программа находится внутри function().
По завершении function()
значение s никогда больше не потребуется и для высвобождения памяти от него избавляются.
Это аналоги классов в Java и Python. Они обозначаются как struct
. Иначе говоря, это пользовательские типы данных, создаваемые для представления типов объектов.
Синтаксис объявления структуры:
struct [identifier] { [fieldName]: [fieldType], [secondFieldName]: [secondFieldType],}
struct
сообщает Rust, что следующее объявление определит тип данных struct.
[identifier]
— это имя типа данных, используемого при передаче параметров. К примеру это string или i32
для строковых и целочисленных типов соответственно.
{}
— фигурные скобки обозначают начало и конец переменных, необходимых для структуры.
[fieldName]
— здесь объявляется первая переменная, которую должны иметь все экземпляры этой структуры. Переменные внутри структуры называются полями.
[fieldType]
— здесь в явном виде определяется тип данных переменной.
Нижеследующий пример включает в себя структуру struct Car,
в которую входит переменная строкового типа brand
и целочисленного year.
struct Car{ brand: String, year: u16,};
Каждый создаваемый экземпляр типа Car
должен иметь значения для этих полей.
Поэтому создадим экземпляр Car
для конкретного автомобиля со значениями для brand
(модели) и year
(года выпуска).
let my_car = Car { brand: String:: from ("BMW"), // с явно заданным строковым типом year: 2009,};
Вот так выглядит полная структура:
fn main () { struct Car { brand: String, year: u16,}; let my_car = Car {brand: String:: from ("BMW"), year: 2009,}; println!("My car is a {} from {}", my_car.brand, my_car.year );}
Как видим, структуры хорошо подходят для хранения данных, которые относятся к тому или иному типу объекта.
Язык Rust позволяет реализовывать широкий спектр задач. В списке отметим:
Иначе говоря, Rust подходит для всех основных задач.
Как ожидается, востребованность языка будет только расти со временем. Ведь многие компании хотят заменить им устаревшие C/C++, и это им удается.
Если же этого вводного материала мало, и вы хотите начать изучать Rust, то можно посмотреть вот эти обучающие видео:
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…