Поради щодо написання бібліотек на Rust

Переклад статті Pascal Hertleif "Good Practices for Writing Rust Libraries" (2015.10.24).

Пройшов приблизно рік, як мене зацікавив Rust, мова програмування від Mozilla Research, зосереджений на вирішенні трьох завдань: безпека, швидкість і паралелізм. Він такий же низькорівневий, як Сі або C++, має хорошу систему типів (з узагальненнями (узагальнення) і типажами (traits)), доброзичливий компілятор і відмінний менеджер пакунків Cargo.
випуску Rust 1.0 пройшло вже пів року (травень 2015): багато бібліотек (пакети, crates), включаючи деякі мої, були опубліковані в центральному регістрі crates.io. Ось непогані практики (ще зарано називати їх "кращими"), які допоможуть іншим людям знаходити, використовувати і доповнювати вашу бібліотеку.

Тримайте свій код в чистоті
Найголовніше, звичайно, це сам ваш код. Rust за замовчуванням перевіряє безліч речей, але є дещо, що можна зробити для поліпшення коду.
У цій статті я не буду обговорювати конкретні поради щодо написання коду. Але якщо вам цікаві поради щодо структурування логіки своєї бібліотеки або архітектурним шаблонів, ви завжди можете подивитися офіційній книзі переклад і Rust Design Patterns.

rustfmt
rustfmt автоматично переформатує ваш код, підвищуючи його зрозумілість людям, особливо не_авторам (машини не в рахунок — вони зрозуміють будь-код, раз він компілюється) (прим. перекладача: і ще припинить марні суперечки про стилях). І він непогано справляється з цим, ну, принаймні, нічого не напартачив в моєму коді. Просто час від часу запускайте цю команду (прим. перекладача: або додайте в редакторі автоматичний прогін rustfmt при збереженні файлів з розширенням
.rs
), щоб ваш код став виглядати аналогічно решті коду на Rust:
$ rustfmt src/lib.rs

Незважаючи на те, що ведуться спроби визначити Єдиний Істинний Стиль Rust (The One True Rust Style), і rustfmt намагається за замовчуванням слідувати їм, ви можете знайти список опцій форматування тут. На даний момент, більша частина моїх проектів використовують наступні параметри (збережіть цей файл rustfmt.toml в корені свого проекту):
format_strings = false
reorder_imports = true

(прим. перекладача: до Речі, тепер при установці через
cargo install rustfmt
стає доступною команда
cargo fmt
. Але особисто мене бісить форматування rusfmt, і до кінця виправити його не допомогли ніякі опції, так що для себе вирішив почекати з його впровадженням.)

Використовуйте більше перевірок
"Перевірки" (lints) — це невеликі плагіни компілятора, які перевіряють ваш код під час складання (в основному на стилістичні помилки). За замовчуванням rustc вже включає деякі попередження, наприклад
dead-code
(попереджає про мертвому коді або
non-snake-case
(попереджає, якщо імена деяких елементів не в змеином_регистре (snake_case)).
Є ще деякі дуже корисні вбудовані перевірки. Ви так само можете перетворити їх у повноцінні помилки збірки, замінивши
#![warn(...)]
на
#![deny(...)]
(дивіться довіднику). Я зазвичай додаю в свої проекти ось ці:
#![deny(missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications)]

Особливо корисний самий перший —
missing_docs
: він спрацьовує, якщо в коді є будь-які недокументовані публічні інтерфейси.
unsafe_code
теж може бути дуже корисний, завдяки йому ваша бібліотека виглядає більш надійною в очах людей.
Список всіх доступних перевірок разом з їх описом можна подивитися у виводі команди
rustc -W help
.

перевірок Богу Перевірок!
Впевнений, що ви вже встигли помітити, що перевірки (lints) — це добре. Тому автори clippy написали ще сотню (насправді, близько 68 для версії 0.0.21 (прим. перекладача: вже більше)). Оскільки clippy є плагіном компілятора, то на даний момент для роботи з ним вам знадобитися нічна збірка rutsc, яку можна викликати різними способами: наприклад, використовуючи cargo-clippy, або додаючи його у вигляді необов'язковою залежно свого проекту.
Мені більше подобається останній варіант, тому додав ось це в свій Cargo.toml:
[dependencies]
clippy = {version = "0.0.21", optional = true}

[features]
default = []
dev = ["clippy"]

Ці налаштування роблять clippy необов'язковою залежністю і включають його, тільки якщо встановлений прапорець
dev
. У головному файлі пакету ви можете додатково включити його ось так:
#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

Складання проекту за допомогою
cargo build --features "dev"
тепер автоматично запускає перевірки clippy. (Само собою, вам потрібно дозволити
unstable_features
, якщо ви до цього забороняли їх).
Попередження: плагіни компілятор в даний момент нестабільні. Якщо ви оновіть нову нічну збірку компілятора, clippy може зламатися. Хоча автори clippy зазвичай все швидко лагодять.
(прим. перекладача: відносно недавно з'явилася онлайн версія clippy, тепер можна не возитися з локальною інсталяцією нічного rustc, і є привід поставити ще одну лычку (badge)
Readme.md
!)
Як і з вбудованими перевірками, деякі перевірки в clippy за замовчуванням вимкнено. Погляньте на документацію clippy, щоб знайти їх повний список і включити цікаві вам!

Тести
У Rust вбудована дивовижна підтримка тестів: ви можете швидко написати тести для своїх модулів прямо всередині них, а cargo автоматично запустить їх (
*.rs
файли в теку
tests/
). О, і приклади в документації або
examples/
) теж будуть протестовані.
Більше тут і сказати особливо нічого. Просто прочитайте главу в офіційній книзі переклад).

Інфраструктура проекту
Крім самого коду є й інші моменти, про які варто подумати при публікації проекту. В першу чергу вони корисні для тих, хто публікує свій код на GitHub, але деякі з цих порад стосуються і без нього.

Метадані Cargo
Перше (і найпростіше), що варто зробити для полегшення пошуку вашої Корисної Бібліотеки, — це заповнити
Cargo.toml
файл. Там є безліч полів з метаданими crates.io. Ось приклад з мого пакету HSL:
[package]
name = "hsl"
version = "0.1.0"
authors = ["Pascal Hertleif <my@email.address>"]
repository = "https://github.com/killercup/hsl-rs.git"
homepage = "https://github.com/killercup/hsl-rs.git"
license = "MIT"
readme = "README.md"
documentation = "http://killercup.github.io/hsl-rs/"
description = "Represent colors in HSL and convert between HSL and RGB."

До речі кажучи, не використовуйте
'*'
версії для залежностей — crates.io відхилить такі пакети, оскільки вони ігнорують семантичне версіонування, на яке спирається Cargo. Також, для спрощення додавання останньої версії пакету в залежності, можна використовувати cargo-edit.

README.md
Люди, що дивляться на стартову сторінку вашого сховища, швидше за все побачать вміст файлу
Readme.md
. Переконайтеся, що в ньому є відповіді на стандартні питання:
  • Що це за проект? Яку проблему він вирішує? Як?
  • Як його встановити/використовувати?
  • Де знайти документацію?
  • Яка у проекту ліцензія?
(прим. перекладача: до Речі, щодо ліцензії: у січні співтовариство Rust перевів більшу частину значущих проектів на двоїсту MIT/Apache-2.0 ліцензію і радить використовувати її надалі за замовчуванням. Аргументація є, наприклад, у задачі бібліотеки cgmath.)
Readme.md
також є відмінним місцем для розміщення невеликих прикладів використання вашої бібліотеки: часто людям зручно починати розбиратися з бібліотекою саме з них. Щоб упевнитися в працездатності прикладів
Readme.md
(та в іншій документації у форматі Markdown), можна використовувати skeptic. Шляхом додавання невеликої хука (hook) у складальний процес Cargo (у файл
build.rs
), ви можете викликати skeptic для перетворення прикладів коду у Markdown файли в звичайні тести. (Подробиці дивіться в документації skeptic.)

Інші метафайли
Не забудьте додати файл
.gitignore
, в якому варто заборонити git'у відстеження директорії
target/
(прим. перекладача: Cargo використовує цю директорію для будь-яких тимчасових файлів і артефактів збірки, так що немає ніякого сенсу фіксувати її в ВКВ). Для бібліотечних пакетів (тобто не дають на виході виконуваний файл) ви повинні ігнорувати і
Cargo.lock
файл (прим. перекладача: почему).
Інший файл, який я намагаюся додавати в кожен проект, — це
.editorconfig
. Я використовую наступні параметри:
root = true

[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4

[*.md]
trim_trailing_whitespace = false
Continuous Integration


Безперервна інтеграція
Якщо ваш відкритий проект розміщується на GitHub'е, ви можете безкоштовно скористатися сервісом безперервної інтеграції Travis CI. Чертовски корисна штука, так як дозволяє настроїти автоматичний запуск тестів у різних середовищах (наприклад, для стабільної, бета і нічний версії Rust або на різних архітектурах) для кожного відправленого зміни (commit) або запиту на включення.
Використовуючи travis-cargo ви можете запускати в Travis тести і бенчмарки, генерувати документацію (і відправляти (push) на GitHub Pages), вважати тестове покриття (і відправляти його в Coveralls).
Я використовую приблизно такий шаблон
.travis.yml
:
sudo: false
language: rust
rust:
- nightly
- beta
- stable
matrix:
allow_failures:
- rust: nightly
before_script:
- |
pip install 'travis-cargo<0.2' --user &&
export PATH=$HOME/.local/bin:$PATH
script:
- |
travis-cargo build &&
travis-cargo test &&
travis-cargo bench &&
travis-cargo --only stable doc
додатки:
apt:
packages:
- libcurl4-openssl-dev
- libelf-dev
- libdw-dev
after_success:
- travis-cargo --only stable doc-upload
- travis-cargo coveralls --no-sudo
notifications:
email:
on_success: never
env:
global:
- TRAVIS_CARGO_NIGHTLY_FEATURE=dev
- secure: # тут всяке зашифроване

(прим. перекладача: в оригіналі це називається "базовою конфігурацією", але я тут не згоден, адже "базова" — це просто
sudo: false\nlanguage: rust
)
Ця конфігурація має кілька опцій спеціально для запуску clippy: нам не важливий результат складання нічним компілятором (інтерфейс плагінів може змінитися, і все зламається), і нічний компілятор викликається з прапором
dev
.
Travis вміє збирати під Linux і Mac OS X. Для тестування під Windows варто поглянути на AppVeyor (він теж безкоштовний для відкритих проектів). (прим. перекладача: Дуже корисний, але страаашно мееедленный, порівняно з Тревісом, сервіс.)

Автогенерація документації
Щоб увімкнути автоматичне завантаження згенерованої документації (тобто відправляти (push) висновок rustdoc в гілку gh-pages), підтримувану в travis-cargo, ви повинні додати змінну оточення
GH_TOKEN
, яка містить маркер доступу (access token) до вашого облікового запису на GitHub (з обмеженими правами). Ви можете створити її ось тут (У мене є по одній для кожного проекту). Щоб зашифрувати маркер, ви можете використовувати утиліту TravisCLI (встановлюється за допомогою
gem install travis
) шляхом запуску її в кореневій директорії проекту (замініть
1234
на свій маркер):
$ travis encrypt "GH_TOKEN=1234" --add env.global

Коли все налаштовано правильно, ви повинні побачити документацію проекту на
ім'я користувача.github.io/имяпроекта
, наприклад killercup.github.io/hsl-rs.
(Прим. перекладача: Запихивание отрендеренной в html документації в гілку проекту, хоч і є дуже поширеною практикою, але мені здається так собі ідеєю. В ідеалі, crates.io повинен сам смикати rustdoc і зберігати/показувати його висновок (в дусі godoc.org). Поки є непоганий милицю у вигляді crates.fyi.)

Homu
Використовуючи безперервну інтеграцію, ви можете бути впевнені, що код в запиті на додавання (pull request) працює і готовий до слитию (merge). А з останніми змінами в гітхабі — захищеними гілками з обов'язковими перевірками (status checks) переклад), ви можете бути впевнені що всі тести пройшли перед злиттям. Але ви не можете бути впевнені, що тести пройдуть після вливання гілки а майстер!
Проект Rust на GitHub використовує інтеграційного робота Bors, щоб вирішити цю проблему: замість злиття запитів на включення самим, вони кажуть Борсу зайнятися цим. Борс бере по одному запиту на включення (у нього є черга), зливає його в поточний master, проженуть всі тести і, якщо вони проходять успішно, надсилає нову версію гілку master. Це означає, що улиття великої кількості запитів на включення може зайняти більше часу, але зате можна бути спокійним, що майстер завжди виконуються всі тести.
Ядро Борса називається homu і може використовуватися і в ваших проектах. Просто додайте користувача homu в співавтори на гітхабі, зареєструйте свій проект на homu.io і зливайте запити на включення за допомогою коментаря
@homu r+
!

Ще трюки
Хочете більше? Добре, ось ще кілька порад:
  • Не дублюйте версію збірки свого додатку в коді. Використовуйте
    env!("CARGO_PKG_VERSION")
    , щоб під час складання отримати версію з Cargo.toml.
  • Дозвольте роботу highfive автоматично вітати нових співавторів на GitHub.
  • Пишіть опису змін (commit message) в "удобном" стилі і використовуйте Clog для автоматичного створення журналу змін. GitCop це робот, який може автоматично перевіряти правильність оформлення описів змін (безкоштовний для відкритих проектів).
  • Виберіть встановлення для Cargo.
(прим. перекладача: Я не дуже згоден щодо використання Clog. Ось, як на мене, відмінний журнал змін, що таке автоматично не вийде зробити. Але, напевно, краще автоматичний журнал змін, ніж взагалі ніякого.)

Висновок
Спасибі, що дочитали до кінця! Сподіваюся, ви використовуєте деякі з описаних тут технік у своїй наступній бібліотеці (на Rust).

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.