% Документация

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

О rustdoc

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

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

Документирование исходного кода

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

/// Создаёт новый `Rc<T>`.
///
/// # Examples
///
///

/// use std::rc::Rc; /// /// let five = Rc::new(5); /// ``` pub fn new(value: T) -> Rc { // здесь реализация }


Этот код генерирует документацию, которая выглядит [так][rc-new]. В приведенном
коде реализация метода была заменена на обычный комментарий. Первое, на что
следует обратить внимание в этом примере, это на использование `///` вместо
`//`. Символы `///` указывают, что это документирующий комментарий.

[rc-new]: http://doc.rust-lang.org/std/rc/struct.Rc.html#method.new

Документирующие комментарии пишутся на Markdown.

Rust отслеживает такие комментарии, и использует их при создании документации.

При документировании таких вещей, как перечисления, нужно учитывать некоторые
особенности работы `rustdoc`. Такой код работает:

```rust
/// Тип `Option`. Подробнее смотрите [документацию уровня модуля](http://doc.rust-lang.org/).
enum Option<T> {
    /// Нет значения
    None,
    /// Некоторое значение `T`
    Some(T),
}

А такой — нет:

/// Тип `Option`. Подробнее смотрите [документацию уровня модуля](http://doc.rust-lang.org/).
enum Option<T> {
    None, /// Нет значения
    Some(T), /// Некоторое значение `T`
}

Вы получите ошибку:

hello.rs:4:1: 4:2 error: expected ident, found `}`
hello.rs:4 }
           ^

Эта досадная ошибка заключается в следующем: комментарии документации распространяются на элементы, расположенные за ними, а в данном примере нет элемента, расположенного после последнего комментария.

Написание комментариев документации

Давайте рассмотрим каждую часть приведенного комментария в деталях:

/// Создаёт новый `Rc<T>`.
# fn foo() {}

Первая строка документирующего комментария должна представлять из себя краткую информацию о функциональности. Одно предложение. Только самое основное. Высокоуровневое.

///
/// Подробности создания `Rc<T>`, возможно, описывающие сложности семантики,
/// дополнительные опции, и всё остальное.
///
# fn foo() {}

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

Специальные разделы

/// # Examples
# fn foo() {}

Далее идут специальные разделы. Они обознаются заголовком, который начинается с #. Существуют три вида заголовков, которые обычно используются. Они не являются каким-либо специальным синтаксисом, на данный момент это просто соглашение.

/// # Panics
# fn foo() {}

Раздел Panics. Неустранимые ошибки при неправильном вызове функции (так называемые ошибки программирования) в Rust, как правило, вызывают панику, которая, в крайнем случае, убивает весь текущий поток (thread). Если ваша функция имеет подобное нетривиальное поведение — т.е. обнаруживает/вызывает панику, то очень важно задокументировать это.

/// # Failures
# fn foo() {}

Раздел Failures. Если ваша функция или метод возвращает Result<T, E>, то хорошим тоном является описание условий, при которых она возвращает Err(E). Это чуть менее важно, чем описание Panics, потому как неудача кодируется в системе типов, но это не значит, что стоит пренебрегать данной возможностью.

/// # Safety
# fn foo() {}

Раздел Safety. Если ваша функция является unsafe, необходимо пояснить, какие инварианты вызова должны поддерживаться.

/// # Examples
///
///

/// use std::rc::Rc; /// /// let five = Rc::new(5); /// ```

fn foo() {}


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

```rust
/// # Examples
///
/// Простые образцы типа `&str`:
///
///

/// let v: Vec<&str> = "И была у них курочка Ряба".split(' ').collect(); /// assert_eq!(v, vec!["И", "была", "у", "них", "курочка", "Ряба"]); /// /// /// Более сложные образцы с замыканиями: /// /// /// let v: Vec<&str> = "абв1где2жзи".split(|c: char| c.is_numeric()).collect(); /// assert_eq!(v, vec!["абв", "где", "жзи"]); /// ```

fn foo() {}


Давайте подробно обсудим блоки кода.

#### Блок кода

Чтобы написать код на Rust в комментарии, используйте символы ```:

```rust
///

/// println!("Привет, мир"); /// ```

fn foo() {}


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

```rust
/// ```c
/// printf("Hello, world\n");
///

fn foo() {}


Это позволит использовать подсветку синтаксиса, соответствующую тому языку,
который был указан в аннотации. Если же это простой текст, то в аннотации
указывается `text`.

Важно выбрать правильную аннотацию, потому что `rustdoc` использует ее
интересным способом: Rust может выполнять проверку работоспособности примеров на
момент создания документации. Это позволяет избежать устаревания примеров.
Предположим, у вас есть код на C. Если вы опустите аннотацию, указывающую, что
это код на C, то `rustdoc` будет думать, что это код на Rust, поэтому он
пожалуется при попытке создания документации.

## Тесты в документации

Давайте обсудим наш пример документации:

```rust
///

/// println!("Привет, мир"); /// ```

fn foo() {}


Заметьте, что здесь нет нужды в `fn main()` или чём-нибудь подобном. `rustdoc`
автоматически добавит оборачивающий `main()` вокруг вашего кода в нужном месте.
Например:

```rust
///

/// use std::rc::Rc; /// /// let five = Rc::new(5); /// ```

fn foo() {}


В конечном итоге это будет тест:

```rust
fn main() {
    use std::rc::Rc;
    let five = Rc::new(5);
}

Вот полный алгоритм, который rustdoc использует для обработки примеров:

  1. Любые ведущие (leading) атрибуты #![foo] остаются без изменений в качестве атрибутов контейнера.
  2. Будут вставлены некоторые общие атрибуты allow, в том числе: unused_variables, unused_assignments, unused_mut, unused_attributes, dead_code. Небольшие примеры часто приводят к срабатыванию этих анализов.
  3. Если пример не содержит extern crate, то будет вставлено extern crate <mycrate>;.
  4. Наконец, если пример не содержит fn main, то оставшаяся часть текста будет обернута в fn main() { your_code }

Хотя иногда этого не достаточно. Например, что насчёт всех этих примеров кода с ///, о которых мы говорили? Простой текст, обработанный rustdoc, выглядит так:

/// Некоторая документация.
# fn foo() {}

А исходный текст на Rust после обработки выглядит так:

/// Некоторая документация.
# fn foo() {}

Да, именно так: вы можете добавлять строки, которые начинаются с #, и они будут скрыты в выводе, но при этом будут использоваться во время компиляции кода. Вы можете использовать это в своих интересах. Если в документирующем комментарии необходимо обратиться к какой-то функции, то ниже нужно будет добавить определение этой функции. В то же время, это делается только для того, чтобы удовлетворить компилятор, поэтому сокрытие ненужных строк в выводе делает пример более ясным. Вы можете использовать эту технику, чтобы детально объяснять длинные примеры, сохраняя при этом тестируемость документации. Например, вот код:

let x = 5;
let y = 6;
println!("{}", x + y);

Ниже приведено отрисованное объяснение этого кода.

Сперва мы устанавливаем x равным пяти:

let x = 5;
# let y = 6;
# println!("{}", x + y);

Затем мы устанавливаем y равным шести:

# let x = 5;
let y = 6;
# println!("{}", x + y);

В конце мы печатаем сумму x и y:

# let x = 5;
# let y = 6;
println!("{}", x + y);

А вот то же самое объяснение, но в виде простого текста:

Сперва мы устанавливаем x равным пяти:

let x = 5;
# let y = 6;
# println!("{}", x + y);

Затем мы устанавливаем y равным шести:

# let x = 5;
let y = 6;
# println!("{}", x + y);

В конце мы печатаем сумму x и y:

# let x = 5;
# let y = 6;
println!("{}", x + y);

Повторяя все части примера, вы можете быть уверены, что ваш пример компилируется, а не просто отображает кусочки кода, которые как-то относятся к той или иной части вашего объяснения.

Документирование макросов

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

/// Паниковать с данным сообщением, если только выражение не является истиной.
///
/// # Examples
///
///

/// # #[macro_use] extern crate foo; /// # fn main() { /// panic_unless!(1 + 1 == 2, "Математика сломалась."); /// # } /// /// ///should_panic /// # #[macro_use] extern crate foo; /// # fn main() { /// panic_unless!(true == false, "Я сломан."); /// # } /// ```

[macro_export]

macro_rules! panic_unless { ($condition:expr, $($rest:expr),+) => ({ if ! $condition { panic!($($rest),+); } }); }

fn main() {}


В нем вы можете заметить три вещи. Во-первых, мы должны собственноручно добавить
строку с `extern crate` для того, чтобы мы могли указать атрибут `#[macro_use]`.
Во-вторых, мы также собственноручно должны добавить `main()`. И наконец, разумно
будет использовать `#`, чтобы закомментировать все, что мы добавили в первых
двух пунктах, что бы оно не отображалось в генерируемом выводе.

### Запуск тестов в документации

Для запуска тестов можно использовать одну из двух комманд

```bash
$ rustdoc --test path/to/my/crate/root.rs
# или
$ cargo test

Все верно, cargo test также выполняет тесты, встроенные в документацию. Тем не менее, cargo test не будет тестировать исполняемые контейнеры, только библиотечные. Это связано с тем, как работает rustdoc: он компонуется с библиотекой, которую надо протестировать, но в случае с исполняемым файлом компоноваться не с чем.

Есть еще несколько полезных аннотаций, которые помогают rustdoc работать правильно при тестировании кода:

/// ```ignore
/// fn foo() {
///

fn foo() {}


Аннотация `ignore` указывает Rust, что код должен быть проигнорирован. Почти во
всех случаях это не то, что вам нужно, так как эта директива носит очень общий
характер. Вместо неё лучше использовать аннотацию `text`, если это не код, или
`#`, чтобы получить рабочий пример, отображающий только ту часть, которая вам
нужна.

```rust
/// ```should_panic
/// assert!(false);
///

fn foo() {}


Аннотация `should_panic` указывает `rustdoc`, что код должен компилироваться, но
выполнение теста должно завершиться ошибкой.

```rust
/// ```no_run
/// loop {
///     println!("Привет, мир");
/// }
///

fn foo() {}


Аннотация `no_run` указывает, что код должен компилироваться, но запускать его
на выполнение не требуется. Это важно для таких примеров, которые должны успешно
компилироваться, но выполнение которых оказывается бесконечным циклом! Например:
«Вот как запустить сетевой сервис».

### Документирование модулей

Rust предоставляет ещё один вид документирующих комментариев, `//!`. Этот
комментарий относится не к следующему за ним элементу, а к элементу, который его
включает. Другими словами:

```rust
mod foo {
    //! Это документация для модуля `foo`.
    //!
    //! # Examples

    // ...
}

Приведённый пример демонстрирует наиболее распространённое использование //!: документирование модуля. Если же модуль расположен в файле foo.rs, то вы, открывая его код, часто будете видеть следующее:

//! Модуль использования разных `foo`.
//!
//! Модуль `foo` содержит много полезной функциональности ла-ла-ла

Стиль документирующих комментариев

Изучите RFC 505 для получения полных сведений о соглашениях по стилю и формату документации.

Другая документация

Все эти правила поведения также применимы и в отношении исходных файлов не на Rust. Так как комментарии пишутся на Markdown, то часто эти файлы имеют расширение .md.

Когда вы пишете документацию в файлах Markdown, вам не нужно добавлять префикс документирующего комментария, ///. Например:

/// # Examples
///
///

/// use std::rc::Rc; /// /// let five = Rc::new(5); /// ```

fn foo() {}


преобразуется в

~~~markdown
# Examples

use std::rc::Rc;

let five = Rc::new(5);

~~~

когда он находится в файле Markdown. Однако есть один недостаток: файлы Markdown
должны иметь заголовок наподобие этого:

```markdown
% Заголовок

Это пример документации.

Строка, начинающаяся с %, должна быть самой первой строкой файла.

Атрибуты doc

На более глубоком уровне, комментарии документации — это синтаксический сахар для атрибутов документации:

/// this
# fn foo() {}

#[doc="this"]
# fn bar() {}

Т.е. представленные выше комментарии идентичны, также как и ниже:

//! this

#![doc="/// this"]

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

Ре-экспорт

rustdoc будет показывать документацию для общедоступного (public) ре-экспорта в двух местах:

extern crate foo;

pub use foo::bar;

Это создаст документацию для bar как в документации для контейнера foo, так и в документации к вашему контейнеру. То есть в обоих местах будет использована одна и та же документация.

Такое поведение может быть подавлено с помощью no_inline:

extern crate foo;

#[doc(no_inline)]
pub use foo::bar;

Управление HTML

Вы можете управлять некоторыми аспектами HTML, который генерирует rustdoc, через атрибут #![doc]:

#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
       html_root_url = "http://doc.rust-lang.org/")];

В этом примере устанавливается несколько различных опций: логотип, иконка и корневой URL.

Опции генерации

rustdoc также содержит несколько опций командной строки для дальнейшей настройки:

  • --html-in-header FILE: включить содержимое FILE в конец раздела <head>...</head>.
  • --html-before-content FILE: включить содержимое FILE сразу после <body>, перед отображаемым содержимым (в том числе строки поиска).
  • --html-after-content FILE: включить содержимое FILE после всего отображаемого содержимого.

Замечание по безопасности

Комментарии в документации в формате Markdown помещаются в конечную веб-страницу без обработки. Будьте осторожны с HTML-литералами:

/// <script>alert(document.cookie)</script>
# fn foo() {}

results matching ""

    No results matching ""