📜 ⬆️ ⬇️

Tips for writing libraries on Rust

Translation of Pascal Hertleif article "Good Practices for Writing Rust Libraries" (2015.10.24).



It has been about a year since I was interested in Rust , a programming language from Mozilla Research, which focuses on solving three problems: security, speed, and concurrency. It is as low-level as C or C ++, has a good type system (with generics and traits), a friendly compiler and an excellent package manager for Cargo .


Half a year has passed since the release of Rust 1.0 (May 2015): many libraries (packages, crates), including some of mine, were published in the central register crates.io . Here are some good practices (it's too early to call them "best") that will help other people find, use and supplement your library.



Keep your code clean


The most important thing, of course, is your code itself. Rust by default checks a lot of things, but there is something else that can be done to improve the code.


In this article I will not discuss specific tips on writing code. But if you are interested in tips on structuring the logic of your library or architectural patterns, you can always look in the official book ( translation ) and Rust Design Patterns .


')

rustfmt


rustfmt automatically reformats your code, increasing its comprehensibility to people, especially non-authors (machines do not count - they will understand any code, since it is compiled) (note of the translator: and will also stop useless discussions about styles). And he copes well with this, well, at least, he didn't mess up anything in my code. Just run this command from time to time (note of the translator: or add the rustfmt automatic run in your editor when saving files with the .rs extension) to make your code look similar to the rest of the Rust code:


 $ rustfmt src/lib.rs 

Although attempts are being made to define the One True Rust Style, and rustfmt tries to follow them by default, you can find a list of formatting options here . At the moment, most of my projects use the following settings (save this in the rustfmt.toml file in the root of your project):


 format_strings = false reorder_imports = true 

(note of translator: By the way, now when installing through the cargo install rustfmt , the cargo fmt subcommand becomes available. But the format of rusfmt makes me angry, and no options helped me to fix it completely, so I decided to wait a little for its implementation.)



Use more checks


The "checks" (lints) are small compiler plugins that check your code at build time (mostly for stylistic errors). By default, rustc already includes some warnings, for example, dead-code (warns of a dead code ) or non-snake-case (warns if the names of some elements are not in the snake-register (snake_case)).


There are some very useful built-in checks. You can also turn them into full-fledged build errors by replacing #![warn(...)] with #![deny(...)] (see the reference book ). I usually add these to my projects:


 #![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, unused_qualifications)] 

The very first one is especially useful - missing_docs : it works if there are any undocumented public interfaces in the code. unsafe_code can also be very useful, thanks to it your library looks more reliable in the eyes of people.


A list of all available checks along with their description can be found in the output of the command rustc -W help .



More Checks God Checks!


I'm sure you have already noticed that checking (lints) is good. Therefore, the authors of clippy wrote another hundred (in fact, about 68 for version 0.0.21 (approx. Translator: already more )). Since clippy is a compiler plugin, at the moment you need to build a nightly rutsc to work with it, which can be called in different ways: for example, using cargo-clippy , or adding it as an optional dependency of your project.


I like the last one more, so I added this to my Cargo.toml:


 [dependencies] clippy = {version = "0.0.21", optional = true} [features] default = [] dev = ["clippy"] 

These settings make clippy an optional dependency and include it only if the dev flag is set. In the main package file you can additionally include it like this:


 #![cfg_attr(feature = "dev", allow(unstable_features))] #![cfg_attr(feature = "dev", feature(plugin))] #![cfg_attr(feature = "dev", plugin(clippy))] 

Building your project with cargo build --features "dev" now automatically starts clippy checks. (Of course, you only need to enable unstable_features if you have previously disabled them).


Warning: compiler plugins are currently unstable. If you upgrade to a new compiler at night, clippy may break. Although the authors of clippy usually fix everything quickly.


(note of the translator: the online version of clippy appeared relatively recently, now you can not bother with the local installation of the nightly rustc, and there is a reason to put one more badge in the Readme.md !)


As with the built-in checks, some checks in clippy are disabled by default. Take a look at the clippy documentation to find their complete list and include interesting ones!



Tests


Rust has amazing test support built in: you can quickly write tests for your modules right inside them, and cargo will automatically launch them ( *.rs files in the tests/ directory). Oh, and the examples in the documentation (or in examples/ ) will also be tested.


There is nothing more to say here. Just read the chapter in the official book ( translation ).



Project infrastructure


In addition to the code itself, there are other points that you should think about when publishing a project. First of all, they are useful for those who publish their code on GitHub , but some of these tips are applicable without it.



Cargo Metadata


The first (and simplest) thing to do to facilitate the search for your Useful Library is to fill in the Cargo.toml file. There are many fields with metadata for crates.io . Here is an example from my HSL package:


 [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.imtqy.com/hsl-rs/" description = "Represent colors in HSL and convert between HSL and RGB." 

By the way, do not use the '*' version for dependencies - crates.io will reject such packages, since they ignore the semantic versioning on which Cargo relies. You can also use cargo-edit to simplify adding the latest version of a package.



README.md


People looking at the start page of your repository will most likely see the contents of the Readme.md file. Make sure that it has answers to standard questions:



(comment of the translator: By the way, about the license: in January the Rust community transferred most of the significant projects to the dual MIT / Apache-2.0 license and advises to use it in the future by default. There are arguments, for example, in the cgmath library task .)


Readme.md also a great place to put small examples of using your library: it is often convenient for people to begin to deal with the library with them. You can use skeptic to verify the performance of the examples in Readme.md (and other documentation in Markdown format). By adding a small hook to the Cargo assembly process (in the build.rs file), you can call skeptic to turn the code samples in Markdown files into regular tests. (For details, see the skeptic documentation.)



Other metafiles


Do not forget to add a .gitignore file, in which git needs to track target/ directory (note the translator: Cargo uses this directory for all temporary files and build artifacts, so there is no point in fixing it in hard currency ). For library packages (that is, those that do not produce an executable file), you should also ignore the Cargo.lock file (note of the translator: why ).


Another file that I try to add to each project is .editorconfig . I use the following settings:


 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


If your open source project is hosted on GitHub, you can use the Travis CI continuous integration service for free. Damn useful thing, because it allows you to configure automatic running of tests in different environments (for example, for a stable, beta and nightly version of Rust or different architectures) for each change sent (commit) or request for inclusion.


Using travis-cargo, you can run tests and benchmarks in Travis, generate documentation (and send (push) it to GitHub Pages ), read test coverage (and send it to Coveralls ).


I use about the following .travis.yml template:


 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 addons: 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: #    

(comment of the translator: in the original it is called the "base configuration", but I do not agree here, because the "base" is just sudo: false\nlanguage: rust )


This configuration has several options specifically for running clippy: the result of the assembly by the night compiler is not critical to us (the plug-in interface can change and everything will break), and the night compiler is called with the dev flag.


Travis can build for Linux and Mac OS X. For testing under Windows, you should take a look at AppVeyor (it is also free for open source projects). (comment of the translator: Very useful, but rather poor, compared to Travis, service.)



Autogenerate Documentation


To enable automatic download of generated documentation (i.e., send (push) the output of rustdoc to the gh-pages branch) maintained in travis-cargo, you must add the environment variable GH_TOKEN , which contains the access token to your account on GitHub (with limited rights). You can create it here (I have one for each project). To encrypt a token, you can use the TravisCLI utility (installed with gem install travis ) by running it in the root directory of the project (replace 1234 with your own marker):


 $ travis encrypt "GH_TOKEN=1234" --add env.global 

When everything is set up correctly, you should see the project documentation in the .imtqy.com/ , for example killercup.imtqy.com/hsl-rs .


(Translator's note: Crashing documentation rendered in html into a project branch, although it is a very common practice, but it seems to me a so-so idea. Ideally, crates.io should rustdoc itself and store / show its output (in the spirit of godoc.org ). While there is a good crutch in the form of crates.fyi .)



Homu


Using continuous integration, you can be sure that the code in the pull request works and is ready to merge. And with the latest changes to the githaba — protected branches with mandatory checks ( translation ), you can be sure that all the tests passed before merging. But you can not be sure that the tests will pass after the injection of the branch and the master!


The Rust project on GitHub uses the integration robot Bors to solve this problem: instead of merging the inclusion requests themselves, they tell Bors to do it. Bors takes one turn-on request (he has a queue), merges him into the current master, runs all the tests and, if they pass successfully, sends a new version to the master branch. This means that the impact of a large number of requests for inclusion may take more time, but you can be sure that all tests are always performed in the wizard.


The core of Bors is called homu and can be used in your projects. Just add the homu user as a collaborator on the githaba, register your project at homu.io and merge the inclusion requests with the @homu r+ comment!



More tricks


Want more? Well, here are some more tips:



(comment of the translator: I do not particularly agree about the use of Clog. Here , as for me, an excellent change log, this will not automatically work. But probably the automatic change log is better than none at all.)



Conclusion


Thank you for reading to the end! I hope you use some of the techniques described here in your next library (on Rust).

Source: https://habr.com/ru/post/280482/


All Articles