
Good Practices for Writing Rust Libraries - jaxondu
https://pascalhertleif.de/artikel/good-practices-for-writing-rust-libraries/
======
jerf
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.)

------
steveklabnik
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.

~~~
Stratoscope
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=10206860)

[https://news.ycombinator.com/item?id=9469713](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.

~~~
steveklabnik
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?)

~~~
Stratoscope
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.

~~~
tatterdemalion
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).

------
merb
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.

~~~
thristian
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.

~~~
merb
> 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.

------
cpeterso
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](https://doc.rust-
lang.org/nightly/std/index.html)

~~~
tatterdemalion
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/](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.

------
jzwinck
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?

~~~
kibwen

      > 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...](https://doc.rust-lang.org/nightly/nomicon/exception-safety.html)
).

    
    
      > 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...](http://www.joshmatthews.net/blog/2015/10/creating-a-c-api-for-a-rust-
library/)

------
Keats
Anyone knows when the compiler plugins will be stable?

~~~
mook
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.)

~~~
Manishearth
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.

