Hacker News new | comments | ask | show | jobs | submit login
Good Practices for Writing Rust Libraries (pascalhertleif.de)
138 points by jaxondu on Oct 25, 2015 | hide | past | web | favorite | 40 comments



This isn't really specific to Rust, but, while I wouldn't necessarily do all this stuff while working through a tutorial on Rust, the start of a brand new language is a great time to learn how to set up this sort of stuff on your code from the very beginning. By definition, you must not have a lot of pre-existing Rust code. :)

Many of these sorts of things are hell to retrofit on to existing code, but if you integrate them into your workflow early, just shy of free over time to use all along. It's "just shy of free" because the tools themselves train you over time so that you get to the point where you start with conforming code, and the tools are just cleaning up occasional slips rather than requiring you to rewrite everything. So, you actually still net out less effort than trying to retrofit later by making fewer mistakes overall in the first place.

I really can't recommend this enough when you're greenfielding something. It's so cheap to start out doing the right thing and so very, very hard to fix it later....

(Obviously, you need to tweak a few things during prototype/exploration, like requirements for docs, but a lot of this stuff is still useful right away.)


A lot of the stuff that interests me is how much this focuses on making things automatic. Use compiler plugins to automatically check your code, use rustfmt to automatically reformat your code, use highfive to automatically greet new contributors, use homu to automatically ensure good CI, use Travis to automatically upload docs, etc.

I like things that are automatic. That way, I don't forget about them.


One of the things that's had the greatest impact on me is a comment D.J. Bernstein made about writing good software:

  For more than a decade I have been systematically identifying error-prone
  programming habits—by reviewing the literature, by analyzing other
  people’s mistakes, and by analyzing my own mistakes—and redesigning my
  programming environment to eliminate those habits.
http://www.aaronsw.com/weblog/001502

Given his track record with writing software largely without security defects, this seems like a promising avenue advancement.


I agree with this. Having things be automatic and unified is great. If there is a standard that Rust enforces regarding style or lints, then any crate published should be easily read by others interested in contributing.

It removes the overhead of having to consider what style the main author used or what lints are in place when compiling it.


rustfmt makes me sad. So do the official Rust and Servo style guides.

The whitespace style they mandate ("column align all the things!") is wildly impractical.

I used to use this exact same style myself, many years ago, but gave it up when I saw how many problems it caused. I'll never go back to this kind of column-aligned formatting.

Here's an example from the rustfmt source code:

    let mut rewrites = try_opt!(subexpr_list.iter()
                                            .rev()
                                            .map(|e| {
                                                rewrite_chain_expr(e,
                                                                   total_span,
                                                                   context,
                                                                   max_width,
                                                                   indent)
                                            })
                                            .collect::<Option<Vec<_>>>());
Here's how I would format that code instead:

    let mut rewrites = try_opt!(
        subexpr_list
            .iter()
            .rev()
            .map( |e| {
                rewrite_chain_expr( e, total_span, context, max_width, indent )
            })
            .collect::<Option<Vec<_>>>()
    );
Or, perhaps if the variable names in the rewrite_chain_expr() call were a little longer, like this:

    let mut rewrites = try_opt!(
        subexpr_list
            .iter()
            .rev()
            .map( |e| {
                rewrite_chain_expr(
                    e,
                    total_span,
                    context,
                    max_width,
                    indent
                )
            })
            .collect::<Option<Vec<_>>>()
    );
Either way, the difference is that I use indentation everywhere that the rustfmt style uses column alignment.

This kind of indentation-based style has numerous advantages over a column-aligned style. I wrote about this at some length previously, so rather than repeat the details, here are my previous comments for anyone who is curious about the rationale for this indentation style:

https://news.ycombinator.com/item?id=10206860

https://news.ycombinator.com/item?id=9469713

Anyway, it isn't the rustfmt style itself that makes me sad.

It's the presumption behind these kind of rigid code reformatters. The bottom line seems to be this collection of notions:

1. Consistent code formatting and whitespace style is vitally important. So important that an automated tool is required to insure that there are no variations in formatting.

2. The exact details of how code is formatted don't matter much. Any formatting style is about as good as any other, as long it is 100% consistent within a project (or language, or whatever). A professional programmer should just find out what the standard is and follow it.

3. There is nothing new to be learned in formatting style. The way we've always done it is good enough, and as good as any other. (See #2.) If you have new and different ideas about whitespace, they are unhelpful and unwelcome. Stop bikeshedding!

And that is what makes me sad.


I have to agree. Adding tons of whitespace is generally going to make things harder to read.

Personally, I'm very much into Ratliff (or banner style) indentation and using two spaces for your indent. I know that 4 is the standard for a lot of people, but my feeling is that this is generally the result of people using VeryLongVariableNamesThatGoOnForever and blocks that go on for a many lines.

The more concise code becomes and the shorter your blocks get, I have found that it becomes much more annoying to have to skip your eye further. This gets especially silly if you have anything with deep nesting.

Ratliff style just makes it so so easy to tell where a block terminates though. I have basically no idea why it isn't wildly popular.

Probably because editors don't support it.


Note that rustfmt is still highly experimental, it didn't even compile on stable Rust until yesterday. I wouldn't actually use it quite yet, and I feel like its mention here in the blog post is mostly as future-proofing for when it is actually ready.


The exact style hasn't been settled yet. We'll be going through RFCs, as usual, for at least the major points. Rustfmt is a start to this, not the final word.

(I would also format like #2, personally. I think Rustfmt might even have a setting to do it this way?)


Oh! In that case I have probably misjudged Rustfmt. My apologies. I was also overdramatizing with the "makes me sad" bit. :-)

I'm not an active Rust user, so probably won't be involved in the RFC process, but I'm glad to hear about it.

OTOH, even for a language I do actively use, I'm a bit reluctant to get involved in a formatting standards process. So many people seem to approach code formatting from a point of view of "This is how we've always done it, this is consistent with other previous standards, so this is how it should be." And then whoever gets the most agreement wins.

I don't often see discussions based on whether there may be actual engineering advantages to one formatting style vs. another. For example, I put spaces inside the parentheses in my function calls and expressions. Many see that as an arbitrary stylistic choice - and object vehemently to it! - but it actually ties in very closely to the column alignment vs. indentation choice in the examples above.

I talked about that in some detail in one of the comments I linked above, so won't belabor it further here. :-)

Maybe that's my beef with coding standards: they tend to take each style decision as an independent, fairly arbitrary choice, instead of listing the goals we're looking for and seeing how different style choices may work together to achieve them.


I don't think there's very much empirical evidence about the engineering advantages of different code style standards. Fortunately, Rust's approach will always be to establish a reasonable compromise and allow you to deviate for your projects (this is why rustfmt is designed to be configurable).


The idea is to pick a standard that people can use as a default, which becomes the "familiar Rust standard". It's mostly for folks who _don't_ care much about formatting nuances.

If you do have a style you prefer, you can always configure your rustfmt to follow that style.


munificent's article about dartfmt discusses this issue and how it scores both and chooses between them: http://journal.stuffwithstuff.com/2015/09/08/the-hardest-pro...


The (adapted) code from that article was used as a stress test for rustfmt in September: https://github.com/nrc/rustfmt/issues/296


Servo doesn't mandate that style. We use a mix of column-aligned and shifted styles. We tend to column align a lot, but we're okay with other ways of aligning as long as it doesn't exceed 120 characters.

> It's the presumption behind these kind of rigid code reformatters.

Rustfmt isn't rigid!

It's highly configurable, file a bug if you want this sort of formatting to be a part of the configuration. I've already filed an issue that asks for a less aggressive line-splitter https://github.com/nrc/rustfmt/issues/448


> I like things that are automatic. That way, I don't forget about them.

Yes! And that way I don't have to _trust anyone_ (including myself) to remember them.

I can't wait for the day where I can safely run clippy on stable and fail my tests when code is formatted incorrectly (according to rustfmt) :)


Clippy running on stable is quite far off. We've had discussions about stabilizing plugins, but it's not happening soon.

Instead, make clippy an optional feature, and compile with `cargo build --features=clippy` on your CI on nightly.


> make clippy an optional feature

Yeah, that's what I'm doing but that is also the reason why nightly is an allowed failure on Travis…

I have high hopes for compiler plugins after the current internal refactorings (HIR, MIR), though :)


HIR/MIR don't really help much, actually. A stable plugin interface would probably be completely different.

And clippy uses a _lot_ of internals, so even if a plugin interface stabilizes there's no guarantee clippy will.


My understanding was that HIR was supposed to _be_ a stable plugin interface.


Perhaps. There's still a long way to go though, since you need a stable AST builder or qquote mechanism, among other things.

And HIR isn't nearly enough for clippy to work on stable.


What Rust and Go still misses is still the good IDE. On the C/C++ side you could even use Eclipse and IntelliJ or Visual Studio. And still if you don't like an IDE you could use an editor, however on rust / golang you are forced to the editor, which could be aweful if the project is grown / big, at least for me. Good practices are helping, however the best editorconfig could help you if your codepage grown too big and you don't remeber the function of a specific struct/class/whatever.


Have you tried writing Rust and Go in a plain text editor and had problems, or are you a happy user of an IDE for another language and waiting for one to be available before you try them out?

Some languages (the prime example being Java) are designed to have simple syntax so that large-scale manipulations can be automated, and as a result you really do need an IDE to work with them: few humans have the patience to find all 59 places in a code-base where some function is called or overridden and add a new parameter.

On the other hand, some languages allow humans the complexity to define their own abstractions, like C++ templates or Scheme macros or Python's reflection and metaprogramming. This extra layer of complexity makes it much harder to write a good IDE, but it also makes an IDE much less necessary: instead of editing all those 59 uses individually, if they were generated by a template or a macro or reflection you can just edit the definition of that thing and get the same result.

I'm guessing Go is in the minimalist-syntax-and-heavy-IDE crowd, although the Go community seems to prefer to write their heavy automation as standalone tools (see: go fmt, go fix). Rust has a fairly impressive macro system, so I would have expected it to be a complex-syntax-and-text-editors language but the Rust authors seem very keen to have some kind of IDE support. Whether they believe Rust actually needs it, or if they just want to check off a commonly-requested feature, I don't know.


> Have you tried writing Rust and Go in a plain text editor and had problems, or are you a happy user of an IDE for another language and waiting for one to be available before you try them out?

I tried both out, but I'm a Scala / Python User mainly and I loved the IDE's I have. Currently especially Autocompletion and my Shortcuts, like search Classes, Types, etc helped me on being productive. Also jump to definition is a feature I often use.

Yes I could even use java without an IDE or Scala however it's not as comfortable and I feel that when I'm programming on an editor I spent a few hours a week tweaking it. While on a IDE i just replace 'some' shortcuts.

Still I think even GO needs a better IDE, since the projects with a few lines are gone and even if you don't write as many lines as in java you still getting a pretty big codebase when you write something useful.

Putting everything in his own library won't help / it will make the problem even scarier since you now need to remember a bigger import path.

Rust is similar and still I love it despite the crates/modules system, since I think it has some rough spots. However they improve the docs regulary which makes things more clear, still it's hard while coming from python/scala/java to have a rust like import path.


>but it also makes an IDE much less necessary

Integrating all the features you need to be productive in a language like C++ (auto complete, compiler output parser, navigation, etc.) and then still lack features like integrated debugging and refactoring is just silly.

Unfortunately IDEs tend to suck on most basic aspect of coding - text editing - and they can be slow. Eclipse is a great example of everything that is wrong with IDEs - you can't even scroll past the end of file (when I first googled how to change this option and read that you can't I had a good laugh) and you can see the editor redrawing on scrolling/input. It really says something when your editor is rendering slower than Atom.

On the other hand good IDE like IntelliJ tools are decently fast and offer a bunch of useful tools you just don't get out using a text editor - the only problem I have with IntelliJ is that CLion is not integrated in to Idea - this means I can't use them on a same project because the project files clash and I have a project that uses Python/TypeScript/C++ it would be awesome if I could develop it all from one IDE (and IntelliJ is great at all those individually)


I have used racer with the Eclipse plugin and when I made changes locally that broke it I felt how absolutely terrible it is to program without basic navigation features like going to the definition of a function or type. I don't want to waste time or brain capacity on something a computer is extremely good at.


I don't know about Go, but I've recently seen this on the Rust side(/site): https://www.rust-lang.org/ides.html


There's also this checklist: http://areweideyet.com/


I know that but racer is not yet as "ready" as i liked. Currently go is far ahead especially https://github.com/DisposaBoy/GoSublime

However I liked rust more, for what I'm doing.


With Atom and Racer you can jump to definitions and code completition is really really good. With atom-build you can also directly trigger a build / run / test. Set up is basically:

apm install language-rust racer build build-cargo

But you'll have to download Racer and Rust's source code, too.


Agreed that rust and go need better IDEs, but I have also seen a lot of c++ code that IDEs are unable to do much with because of the language's complexity and ambiguity.

VS is easily confused by typedefs and forward declarations, in my experience.


There is an intellij plugin for golang - https://plugins.jetbrains.com/plugin/5047?pr=idea


It would be nice if the Rust documentation had a centralized list of std and core traits. For example, I see a summary of the std crate's exported types, modules, and macros but no traits. Such a list would be useful for library developers who want to maximize their library's potential for reuse by implementing all relevant core and std traits.

https://doc.rust-lang.org/nightly/std/index.html


Hopefully this will help:

The traits defined in std are listed in the modules that they are defined in, just like all the other kinds of types (structs and enums) defined in std.

The top level modules of std are themselves linked from the main index of std, of course, and you navigate through them to find the types you care about. The primitive types have links on the main index of std because they otherwise wouldn't appear in the API documentation (since they are primitive and thus aren't defined in any module), but most types are not (e.g. HashSet<T> isn't linked from the main index). The macros have links on the main index of std because they aren't imported using the module system.

What you're probably refering to though are the traits re-exported by the std prelude, which are automatically imported into every Rust module. The prelude is described on this page (linked to from the std main index):

https://doc.rust-lang.org/nightly/std/prelude/

The prelude mostly imports traits, but it also imports a few other kinds of items as well: some types (like Option<T> and String) and at least one function (drop()).

But what traits your type should implement is not really connected with what traits are imported in the prelude.


What about good practice for the actual writing of Rust library code? I know C++ and there we have advice such as not reordering virtual methods, not using STL types in public APIs, not reusing enum values, exception safety, RAII, etc. What about in Rust? What are some guidelines for writing libraries which will give their users maximum convenience and safety? Is there a stable ABI, and if so what do library implementers need to know about it? How about writing C APIs in Rust?


  > not reordering virtual methods, not using STL types in 
  > public APIs, not reusing enum values
There's no reason to worry about any of these in Rust. The design of the language is intended to minimize and discourage footguns in general.

  > exception safety
Exception safety is only a concern from within `unsafe` blocks. If you're using `unsafe` blocks at all, 1) really try not to, 2) if you still must use them, read The Rustonomicon first (here's the specific chapter on exception safety: https://doc.rust-lang.org/nightly/nomicon/exception-safety.h... ).

  > What are some guidelines for writing libraries which
  > will give their users maximum convenience and safety?
Not many guidelines spring to mind. The first is still "don't use `unsafe` blocks if you can help it" (the OP gets into this by mentioning the lint that denies unsafe code in your program, which gives your users an easy way to determine your policy on avoiding unsafe code). The second is "don't panic in library code, use the Result type instead".

  > Is there a stable ABI
Nope! Far-future work.

  > How about writing C APIs in Rust?
Now this is something that could use real documentation and advice like you're requesting. Nothing authoritative springs to mind, but here's a recent experience report on the process to get people started: http://www.joshmatthews.net/blog/2015/10/creating-a-c-api-fo...


Anyone knows when the compiler plugins will be stable?


We don't have a timeframe yet, but the internal refactorings we're working on will help with this, and is one of the reasons we're doing it.


Still a while to go. We've got some ideas, but nothing concrete yet, and it's hard to do in a backwards compatible form. And even if it stabilizes, there's no guarantee that things like clippy will be able to use it since clippy relies on a _lot_ of internals.

If you want to use a plugin like clippy, I'd suggest you make it an optional dependency (see the README) and do `cargo build --features=clippy` on nightly on your CI.


Related, at what point will "use a stable build, not a nightly" be a good practice for libraries? Actually, do people mainly use nightlies or releases for development? (Of libraries and apps, not the toolchain itself, obviously.)


It already is a good practice.

Most people seem to use stable.

I work on tools so I prefer nightlies (but I use multirust so it's easy to switch). And Servo (which I also work on) uses its own snapshots.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: