Содержание


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

Comments

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

Тому, кто имеет опыт написания программ на одном из С-подобных языков (C++, Java, JavaScript, C#, PHP), Rust покажется знакомым. Код группируется в блоки, ограниченные фигурными скобками; здесь есть управляющие структуры для ветвления и зацикливания, такие как, например, привычные if и while; вызовы функций записываются в форме myfunc( arg1, arg2 ); операторы почти такие же и имеют в своём большинстве те же уровни приоритета выполнения, что и в С; комментарии также пишутся в С-форматах; имена модулей разделяются двумя символами двоеточия (::), как при использовании С++.

1. Общие правила синтаксиса

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

Листинг 1. Тело управляющей структуры обязательно должно быть взято в фигурные скобки
fn main() {
  /* простой цикл */
  loop {
    if my_mod::random() < 10 {
      return;
    }
  }
}

2. Локальные переменные

Ключевое слово let определяет локальную переменную. По умолчанию переменные являются неизменяемыми (как ни парадоксально это звучит). Для того, чтобы определить локальную переменную, значение которой может измениться в дальнейшем, следует воспользоваться комбинацией ключевых слов let mut. В листинге 2 приведён фрагмент кода с использованием обычной и изменяемой локальных переменных.

Листинг 2. Определение и использование обычной и изменяемой локальных переменных
let limit = 10;
let mut count = 0;

while count < limit {
  io::println( fmt!("count = %?", count) );
  count += 1;
}

Несмотря на то, что Rust почти всегда может сам сделать логический вывод ("догадаться") о типах локальных переменных по их определениям, пользователь имеет возможность явно указывать тип переменной после двоеточия, сопровождающего имя переменной. С другой стороны, статические элементы (static) всегда требуют явного указания типа. Примеры определений переменных различных типов продемонстрированы в листинге 3.

Листинг 3. Определения статических и обычных локальных переменных
static pi: float = 3.14159265358979323846;
let area = pi * 12.0 * 12.0;
let area: int = 333;

Объявления и определения локальных переменных могут замещать (фактически отменять) более ранние определения, как показано в предыдущем примере в листинге 3: сначала переменная area была неявно объявлена, как float, затем следует второе определение уже с явным указанием типа int. Если допустить, что фрагмент кода из листинга 3 является составной частью компилируемой программы, то компилятор определит, что второе объявление переменной area никак не используется, и выдаст соответствующее предупреждение (поскольку ситуация очень похожа на ошибку программиста, совершённую по невнимательности). В тех случаях, когда неиспользуемые переменные введены в код намеренно, к их именам может быть присоединён префикс в виде символа подчёркивания, чтобы предупреждение компилятора не выводилось, например:

let _area: int = 333;

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

Листинг 4. Стили записи переменных и типов
let my_variable = 100;
type MyType = int;  // определение пользовательского типа

3. Выражения и точки с запятой, как разделители

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

Листинг 5. Запись кода в стиле С
let price;
if feature == "противоударный" {
  price = 33.50;
} else if feature == "водонепроницаемый" {
  price = 29.25;
} else {
  price = 25.00;
}

Но при использовании синтаксиса Rust нет никакой необходимости повторять несколько раз имя переменной price (см. листинг 6).

Листинг 6. Компактная запись с учётом синтаксиса Rust
let price =
  if feature == "противоударный" {
    33.50
  } else if feature == "водонепроницаемый" {
    29.25
  } else {
    25.00
};

Оба приведённых выше в листингах 5 и 6 фрагмента кода полностью равнозначны: они присваивают значение переменной price в зависимости от результата вычисления заданных условных выражений. Следует отметить, что во втором фрагменте внутри блоков нет ни одной точки с запятой. Это очень важно: отсутствие точки с запятой после последней инструкции в фигурных скобках означает, что весь этот блок выдаёт значение самого последнего выражения.

Другими словами, точка с запятой в Rust предназначена для игнорирования значения выражения, после которого она размещена. Следовательно, если ветви конструкции if записывать в виде { 4; }, то в примере из листинга 6 переменной price было бы просто присвоено значение () (nil или void). Но без точки с запятой каждая ветвь имеет собственное значение, то есть, price получает то значение, для которого выполнено заданное условие.

Таким образом, всё, что не является объявлением (declaration) (а объявления начинаются с ключевых слов: let для переменных, fn для функций или представляют любые именованные элементы самого верхнего уровня, такие как признаки или свойства (traits), перечислимые типы (enum types) и константы), является выражением, в том числе и тела функций (см. листинг 7).

Листинг 7. Пример тела функции, как выражения
fn is_zero( x: int ) -> bool {
  // В инструкции return нет необходимости.
  // Результат следующего выражения используется, как возвращаемое значение.
  x == 0
}

4. Простые базовые типы и литералы

В языке Rust имеются два общих базовых целочисленных типа — знаковый (signed) и беззнаковый (unsigned): int и uint, а также их 8-, 16-, 32- и 64-битовые варианты, обозначаемые как i8, u16 и т.д. Целочисленные значения могут быть записаны в десятичной (144), шестнадцатеричной (0x90) или бинарной (0b10010000) формах. Каждый целочисленный тип имеет соответствующий литеральный суффикс, который может быть использован для явного указания типа литерала: i для int, u для uint, i8 для 8-битового типа int.

При отсутствии целочисленного литерального суффикса Rust будет сам определять (логически выводить) целочисленный тип на основе аннотаций типа и сигнатур функций в окружающей его программе. Если какая бы то ни было информация о типе полностью отсутствует, Rust предположит, что целочисленный литерал без суффикса имеет тип int (см. листинг 8).

Листинг 8. Примеры явного и неявного определений типов
let a = 1;        // a имеет тип int (по предположению)
let b = 10i;      // b имеет тип int, по суффиксу i
let c = 100u;     // c имеет тип uint
let d = 1000i32;  // d имеет тип i32

Для значений с плавающей точкой существуют три типа: float, f32 и f64. Числа с плавающей точкой записываются в следующих формах: 0.0, 1e6 или 2.1e-4. Как и для целочисленных значений, для литералов с плавающей точкой правильный тип выводится из контекста. Могут быть использованы суффиксы f, f32 и f64 для создания литералов заданного типа.

Ключевые слова true и false представляют литералы логического типа bool.

Символы, имеющие тип char, являются 4-байтовыми unicode-элементами, литералы которых записываются между одиночными кавычками, как например, 'x'. Как и С, Rust понимает экранированные символы, использующие префиксный символ обратный слэш, то есть символы \n, \r и \t. Эти же экранированные esc-последовательности разрешается использовать в строковых литералах, заключённых в двойные кавычки. Более подробно о строковых литералах будет сказано в следующих статьях цикла.

Специальный тип nil, записываемый, как (), соответствует единственному значению, также записываемому в виде ().

5. Операторы

Набор операторов в Rust вполне привычен для любого программиста. Арифметические: * (умножение), / (деление), % (взятие остатка от деления), + (сложение), - (вычитание) и унарный префиксный оператор — для смены знака числа. Как и в С, поддерживаются битовые операторы >>, <<, &, | и ^.

Следует особо отметить тот факт, что для целочисленных значений оператор ! выполняет инвертирование всех битов числа (как оператор ~ в С).

Операторы сравнения также выглядят весьма привычными: ==, !=, <, >, <=, >=. Возможны краткие формы записи логических операторов && (and) и || (or).

Для приведения типов в Rust используется бинарный оператор as. Он берёт выражение в левой части и тип из правой части и, если существует имеющее смысл преобразование, выполняет такое преобразование для результата текущего выражения. Пример допустимого преобразования типа приведён в листинге 9.

Листинг 9. Пример корректного преобразования типов
let x: float = 4.0;
let y: uint = x as uint;
assert!( y == 4u );

6. Расширения синтаксиса

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

Расширение синтаксиса fmt! поддерживает большинство директив и шаблонов, используемых в С-функции printf, но в отличие от printf сообщает об ошибках во время компиляции, если типы, указанные в директивах, не совпадают с типами аргументов. Примеры использования fmt! приведены в листинге 10.

Листинг 10. Применение синтаксического расширения fmt!
io::println( fmt!( "%s равна %d", "Общая сумма", 89 ));

// директива %? позволяет выводить любой тип (заранее неизвестный)
io::println( fmt!( "Результат идентификации: %?", unidentified_flying_object ));

Программист может определять собственные расширения синтаксиса с помощью системы макро. Более подробно этот вопрос будет рассматриваться в одной из следующих статей.

Заключение

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


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


Похожие темы


Комментарии

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

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