1. 1. Introduction
  2. 2. Введение
  3. 3. C чего начать
  4. 4. Изучение Rust
    1. 4.1. Угадайка
    2. 4.2. Обедающие философы
    3. 4.3. Вызов кода на Rust из других языков
  5. 5. Синтаксис и семантика
    1. 5.1. Связывание имён
    2. 5.2. Функции
    3. 5.3. Простые типы
    4. 5.4. Комментарии
    5. 5.5. Конструкция `if`
    6. 5.6. Циклы
    7. 5.7. Владение
    8. 5.8. Ссылки и заимствование
    9. 5.9. Время жизни
    10. 5.10. Изменяемость
    11. 5.11. Структуры
    12. 5.12. Перечисления
    13. 5.13. Конструкция `match`
    14. 5.14. Шаблоны сопоставления `match`
    15. 5.15. Синтаксис методов
    16. 5.16. Вектора
    17. 5.17. Строки
    18. 5.18. Обобщённое программирование
    19. 5.19. Типажи
    20. 5.20. Типаж `Drop`
    21. 5.21. Конструкция `if let`
    22. 5.22. Типажи-объекты
    23. 5.23. Замыкания
    24. 5.24. Универсальный синтаксис вызова функций
    25. 5.25. Контейнеры и модули
    26. 5.26. `const` и `static`
    27. 5.27. Атрибуты
    28. 5.28. Псевдонимы типов
    29. 5.29. Приведение типов
    30. 5.30. Ассоциированные типы
    31. 5.31. Безразмерные типы
    32. 5.32. Перегрузка операций
    33. 5.33. Преобразования при разыменовании
    34. 5.34. Макросы
    35. 5.35. Сырые указатели
    36. 5.36. Небезопасный код
  6. 6. Эффективное использование Rust
    1. 6.1. Стек и куча
    2. 6.2. Тестирование
    3. 6.3. Условная компиляция
    4. 6.4. Документация
    5. 6.5. Итераторы
    6. 6.6. Многозадачность
    7. 6.7. Обработка ошибок
    8. 6.8. Выбор гарантий
    9. 6.9. Интерфейс внешних функций
    10. 6.10. Типажи `Borrow` и `AsRef`
    11. 6.11. Каналы сборок
    12. 6.12. Using Rust without the standard library
  7. 7. Нестабильные возможности Rust
    1. 7.1. Плагины к компилятору
    2. 7.2. Встроенный ассемблерный код
    3. 7.3. Без stdlib
    4. 7.4. Внутренние средства
    5. 7.5. Элементы языка
    6. 7.6. Продвинутое руководство по компоновке
    7. 7.7. Тесты производительности
    8. 7.8. Синтаксис упаковки и шаблоны `match`
    9. 7.9. Шаблоны `match` для срезов
    10. 7.10. Ассоциированные константы
    11. 7.11. Пользовательские менеджеры памяти
  8. 8. Глоссарий
  9. 9. Syntax Index
  10. 10. Библиография

Плагины к компилятору

Введение

rustc, компилятор Rust, поддерживает плагины. Плагины — это разработанные пользователями библиотеки, которые добавляют новые возможности в компилятор: это могут быть расширения синтаксиса, дополнительные статические проверки (lints), и другое.

Плагин — это контейнер, собираемый в динамическую библиотеку, и имеющий отдельную функцию для регистрации расширения в rustc. Другие контейнеры могут загружать эти расширения с помощью атрибута #![plugin(...)]. Также смотрите раздел rustc::plugin с подробным описанием механизма определения и загрузки плагина.

Передаваемые в #![plugin(foo(... args ...))] аргументы не обрабатываются самим rustc. Они передаются плагину с помощью метода args структуры Registry.

В подавляющем большинстве случаев плагин должен использоваться только через конструкцию #![plugin], а не через extern crate. Компоновка потянула бы внутренние библиотеки libsyntax и librustc как зависимости для вашего контейнера. Обычно это нежелательно, и может потребоваться только если вы собираете ещё один, другой, плагин. Статический анализ plugin_as_library проверяет выполнение этой рекомендации.

Обычная практика — помещать плагины в отдельный контейнер, не содержащий определений макросов (macro_rules!) и обычного кода на Rust, предназначенного для непосредственно конечных пользователей библиотеки.

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

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

Давайте напишем плагин roman_numerals.rs, который реализует целочисленные литералы с римскими цифрами.

#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private)]

extern crate syntax;
extern crate rustc;

use syntax::codemap::Span;
use syntax::parse::token;
use syntax::ast::{TokenTree, TtToken};
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::build::AstBuilder;  // типаж для expr_usize
use rustc::plugin::Registry;

fn expand_rn(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
        -> Box<MacResult + 'static> {

    static NUMERALS: &'static [(&'static str, u32)] = &[
        ("M", 1000), ("CM", 900), ("D", 500), ("CD", 400),
        ("C",  100), ("XC",  90), ("L",  50), ("XL",  40),
        ("X",   10), ("IX",   9), ("V",   5), ("IV",   4),
        ("I",    1)];

    let text = match args {
        [TtToken(_, token::Ident(s, _))] => token::get_ident(s).to_string(),
        _ => {
            cx.span_err(sp, "аргумент должен быть единственным идентификатором");
            return DummyResult::any(sp);
        }
    };

    let mut text = &*text;
    let mut total = 0;
    while !text.is_empty() {
        match NUMERALS.iter().find(|&&(rn, _)| text.starts_with(rn)) {
            Some(&(rn, val)) => {
                total += val;
                text = &text[rn.len()..];
            }
            None => {
                cx.span_err(sp, "неправильное римское число");
                return DummyResult::any(sp);
            }
        }
    }

    MacEager::expr(cx.expr_u32(sp, total))
}

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("rn", expand_rn);
}Run

Теперь мы можем использовать rn!() как любой другой макрос:

#![feature(plugin)]
#![plugin(roman_numerals)]

fn main() {
    assert_eq!(rn!(MMXV), 2015);
}Run

У этого подхода есть преимущества относительно простой функции fn(&str) -> u32:

В дополнение к процедурным макросам, вы можете определять новые атрибуты derive и другие виды расширений. Смотрите раздел Registry::register_syntax_extension и документацию перечисления SyntaxExtension. В качестве более продвинутого примера с макросами, можно ознакомиться с макросами регулярных выражений regex_macros.

Советы и хитрости

Некоторые советы по отладке макросов применимы и в случае плагинов.

Можно использовать syntax::parse, чтобы преобразовать деревья токенов в высокоуровневые элементы синтаксиса, вроде выражений:

fn expand_foo(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
        -> Box<MacResult+'static> {

    let mut parser = cx.new_parser_from_tts(args);

    let expr: P<Expr> = parser.parse_expr();Run

Можно просмотреть код парсера libsyntax, чтобы получить представление о работе инфраструктуры разбора.

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

Вызов ExtCtxt::span_fatal сразу прервёт компиляцию. Вместо этого, лучше вызвать ExtCtxt::span_err и вернуть DummyResult, чтобы компилятор мог продолжить работу и обнаружить дальнейшие ошибки.

Вы можете использовать span_note и syntax::print::pprust::*_to_string чтобы напечатать синтаксический фрагмент для отладки.

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

Плагины статических проверок

Плагины могут расширять инфраструктуру статических проверок Rust, предоставляя новые проверки стиля кодирования, безопасности, и т.д. Полный пример можно найти в src/test/auxiliary/lint_plugin_test.rs. Здесь мы приводим его суть:

declare_lint!(TEST_LINT, Warn,
              "Предупреждать об элементах, названных 'lintme'");

struct Pass;

impl LintPass for Pass {
    fn get_lints(&self) -> LintArray {
        lint_array!(TEST_LINT)
    }

    fn check_item(&mut self, cx: &Context, it: &ast::Item) {
        let name = token::get_ident(it.ident);
        if name.get() == "lintme" {
            cx.span_lint(TEST_LINT, it.span, "элемент называется 'lintme'");
        }
    }
}

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_lint_pass(box Pass as LintPassObject);
}Run

Тогда код вроде

#![plugin(lint_plugin_test)]

fn lintme() { }Run

выдаст предупреждение компилятора:

foo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default
foo.rs:4 fn lintme() { }
         ^~~~~~~~~~~~~~~

Плагин статического анализа состоит из следующих частей:

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

Статические проверки, определяемые плагинами, управляются обычными атрибутами и флагами компилятора, т.е. #[allow(test_lint)] или -A test-lint. Эти идентификаторы выводятся из первого аргумента declare_lint!, с учётом соответствующих преобразований регистра букв и пунктуации.

Вы можете выполнить команду rustc -W help foo.rs, чтобы увидеть весь список статических проверок, известных rustc, включая те, что загружаются изfoo.rs.