Содержание


Rust - новый язык программирования: Часть 1. Общее описание, характеристики и свойства

Comments

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

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

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

1. Предварительный обзор функциональных возможностей языка

Характеристики системы типов данных: static, nominal, linear, algebraic, locally inferred (локально выводимые).

Безопасная работа с памятью обеспечивается отстутствием null-указателей или "висящих" указателей, также исключено переполнение буферов.

Обобщённая настраиваемая параметризация типов с использованием классов типов.

Обработка исключений выполняется без восстановления состояния, без "обратной раскрутки" (unwinding), с изоляцией (обрабатываемой) задачи.

Модель памяти: необязательный к применению сборщик мусора (GC), локальный относительно одной задачи, безопасные типы указателей с анализом их области видимости.

Компилятор и все вспомогательные инструментальные средства публикуются под двойной лицензией: MIT / Apache 2.

2. Краткая история

Rust начинался, как личный проект разработчика компании Mozilla Грэйдона Хоара (Graydon Hoare) в 2006 году. Компания заинтересовалась разработкой в 2009 г. и взяла проект под свою опеку. В 2010 г. основной акцент разработки был перенесён на создание компилятора rustc, который мог бы компилировать себя сам (до этого компилятор для сборки Rust был написан на языке OCaml), в это же время впервые была опубликована информация о новом языке программирования Rust. И уже в 2011 г. задача самокомпиляции была успешно решена. В качестве бэкэнда rustc использует LLVM.

В январе 2012 г. был представлен первый альфа-релиз компилятора Rust. На момент написания данной статьи текущей являлась версия 0.6, выпущенная в апреле 2013 г. Грэйдон Хоар и компания Mozilla планируют представить первую стабильную версию языка в конце 2013 года.

3. Основные концепции Rust

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

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

  • Определение типов по предположению — в объявлениях локальных переменных указание типа не является обязательным.
  • Безопасное параллельное выполнение, основанное на задачах (task)— так называемые "лёгкие" задачи (lightweight tasks) в Rust не используют память совместно, вместо этого они обмениваются сообщениями.
  • Эффективные и гибкие замыкания обеспечивают выполнение итераций и других управляющих конструкций.

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

Полиморфизм — в Rust имеются функции с параметрами-типами, а также типы данных, классы типов и интерфейсы в объектно-ориентированном стиле.

Как бы то ни было, одной из основополагающих концепций Rust является максимально возможное повышение уровня безопасности при написании кода. Вообще говоря, дизайн языка может повысить уровень безопасности двумя способами: либо упростить написание хорошего кода, либо затруднить написание плохого кода. Первый подход выглядел весьма успешно на протяжении многих лет в формах структурного программирования, улучшенных и расширенных системах типов и усовершенствованной инкапсуляции, которые позволяли сформулировать и реализовать идеи и задачи ясно и просто с помощью концепций высокого уровня. Второй подход не имел столь громкого публичного успеха, во многом из-за того, что он требует исключения тех функциональных возможностей, которые напрямую ведут к написанию плохого кода. Разумеется, проблема состоит в том, что эти потенциально опасные функциональные возможности могут быть и чрезвычайно удобными и полезными. Классическим примером всегда являлся оператор goto, который уже давно известен, как наиболее очевидный источник ошибок, тем не менее большинство широко используемых процедурных языков продолжают включать его в свой синтаксис, поскольку без этого оператора достаточно быстро возникают трудности (тем не менее Rust, так же как и Python, обошелся без goto).

Rust выбирает второй подход к обеспечению безопасности. Ряд функциональных возможностей, присутствующих в языках типа C, и остающихся, несмотря ни на что, в уделяющих большее внимание безопасности языках типа Java, запрещены или по крайней мере серьёзно ограничены в Rust. Эти потери в значительной степени компенсируются другими функциональными средствами языка, и упомянутые выше потенциально опасные возможности становятся ненужными. Один из аспектов такого подхода очевиден: Rust старается генерировать ошибки компиляции в тех ситуациях, где использование прочих языков приводит к ошибкам времени выполнения (или аварийным завершениям программ). Это не только снижает накладные расходы во время выполнения, но и должно сократить количество обращений клиентов-заказчиков в службы поддержки.

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

Развивая идею модуля System в языке Modula-2 (этот модуль обеспечивает доступ к так называемой "функциональности низкого уровня"), Rust позволяет объявлять функции и блоки кода, как "небезопасные" (unsafe). В области такого небезопасного кода не применяется большинство ограничений, которые как раз и делают Rust безопасным языком. Это можно рассматривать как признание неудачи при создании действительно безопасного языка, но такое умозаключение будет чрезмерно упрощённым, если не сказать примитивным. С теоретической точки зрения теорема Гёделя о неполноте позволяет высказать предположение о том, что любой язык программирования, достаточно богатый возможностями для того, чтобы выразить всё, что хочет сказать программист, обладает вместе с тем и полным спектром свойств, из-за которых его невозможно считать "безопасным". Невозможность абсолютной безопасности подтверждается и практикой.

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

4. Безопасность по умолчанию

Если рассматривать конкретные способы, которыми Rust обеспечивает безопасность кода, то первым обращает на себя внимание ограничение, касающееся переменных.

Многие языки программирования используют ключевые слова const (C, C++, Go) или final (Java) для объявления того, что некоторое имя, один раз связанное, всегда будет ссылаться на одно и то же значение (фактически таким образом объявляется константа). Rust использует противоположный подход: связывания неизменяемы по умолчанию, а чтобы объявить переменную изменяемой, обязательно требуется наличие ключевого слова mut. Таким образом, после объявления

let phi = 1.61803398874989484820  // золотое сечение

любые изменения значения phi будут запрещены, а объявление

let mut length = 2.05

позволяет в дальнейшем изменять значение переменной length. Возможно, в начальной стадии знакомства с языком Rust это может показаться нонсенсом, но необходимость явного указания ключевого слова mut заставляет программиста задуматься о пользе разрешения/запрещения модификации того или иного значения, и после некоторой практики становится вполне уместной.

Для некоторых структур данных свойство изменяемости/неизменяемости наследуется через ссылки, а для элементов структур и через родительские структуры, поэтому если неизменяемая ссылка связана с определёнными объектами, то компилятор проследит за тем, чтобы весь объект в целом оставался неизменяемым.

5. Демонстрационный пример

Для того, чтобы получить общее представление о синтаксисе и внешнем виде кода на языке Rust предлагается небольшая простая программа в листинге 1.

Листинг 1. Демонстрационный пример кода Rust
fn main() {
  for ["Аня", "Боря", "Вова", "Гена", "Даша", "Егор", "Женя"].each |&name| {
    do spawn {
      for ["Привет", "Поговорим?", "Не возражаю", "Что?", "Придумай что-нибудь", 
           "Это уже было", ":-)", "Ну ты сказал!"].each |&rep| {
        print(fmt!("<%s>: %s\n", name, rep)) 
      }
    }
  }
}

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

6. С чего начать

В настоящее время компилятор Rust можно самостоятельно собрать и установить из tar-архива в различных дистрибутивах Linux. Следует отметить, что поскольку компилятор Rust написан на самом Rust, сборка производится посредством уже предварительно скомпилированной текущей версии данного компилятора (созданной на предыдущей стадии разработки), поэтому потребуется постоянное Интернет-соединение на время компиляции и установки (при этом текущая операционная система должна обеспечивать выполнение бинарных файлов компилятора).

После завершения процесса установки в локальном каталоге /usr/local/bin будут размещены следующие программы:

  • rustc— компилятор языка Rust
  • rustdoc— инструмент для создания документации к API
  • rustpkg— менеджер пакетов Rust
  • rusti— интерпретатор Rust, обеспечивающий цикл REPL
  • rust— инструмент, который работает и как единый унифицированный интерфейс для всех перечисленных выше программ, и как средство выполнения нескольких общих сценариев командной строки.

6.1. Первая программа. Компиляция

По соглашению файлам с исходным кодом программ на языке Rust присваиваются расширения .rs. Допустим, что имеется файл hello.rs, содержащий исходный код, приведённый в листинге 2.

Листинг 2. Самая простая программа на языке Rust
fn main() {
  println( "hello" );
}

Если компилятор Rust был установлен правильно, то запуск команды

rustc hello.rs

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

Компилятор Rust при обнаружении ошибки старается выдать как можно больше полезной информации. Если в код листинга 2 намеренно внести ошибку (например, вместо io::println вписать имя какой-нибудь несуществующей функции), то при попытке компиляции сообщения об ошибках будут выглядеть приблизительно так, как показано в листинге 3.

Листинг 3. Попытка компиляции программы с ошибками
$ rustc hello.rs 
hello.rs:2:2: 2:25 error: unresolved name: `print_with_my_own_rules`.
hello.rs:2   print_with_my_own_rules( "hello" );
             ^~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
$

В самой простой форме исходный код программы на языке Rust представляет собой текстовый файл с расширением .rs, в котором содержатся определения некоторых типов данных и функций. Если в таком файле имеется функция main, то он может быть скомпилирован в выполняемый файл. По правилам Rust не допускается наличие кода, для которого отстутствует объявление (declaration) на самом верхнем уровне структуры файла, то есть, все инструкции программы обязательно должны быть записаны внутри тела функции. Кроме того, Rust-программы могут быть скомпилированы, как библиотеки, и могут быть включены в другие программы.

6.2. Использование инструментального средства rust

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

Программа rust предоставляет централизованный доступ ко всем прочим инструментам, а также удобные быстрые клавиши для прямого запуска на компиляцию и выполнение файлов с исходным кодом. Например, если в текущем рабочем каталоге есть файл foo.rs, то команда rust foo.rs сначала попытается скомпилировать этот файл, и если попытка будет успешной, то выполнит полученный бинарный файл.

Чтобы получить список всех допустимых команд, нужно просто запустить программу rust без аргументов.

Заключение

Rust позиционируется, как универсальный и надёжный язык программирования, и компания Mozilla предлагает его в качестве основного средства web-разработки. Пожалуй, впервые в языке программирования главное внимание уделено безопасности и однозначности применяемых средств, а не "трюкам и уловкам". В следующих четырёх статьях (с 2 по 5) будет рассматриваться основной синтаксис Rust.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=946849
ArticleTitle=Rust - новый язык программирования: Часть 1. Общее описание, характеристики и свойства
publish-date=09302013