Good Practices for Writing Rust Libraries
pascalhertleif.de
pascalhertleif.de
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.)
I like things that are automatic. That way, I don't forget about them.
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/001502Given his track record with writing software largely without security defects, this seems like a promising avenue advancement.
It removes the overhead of having to consider what style the main author used or what lints are in place when compiling it.
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 would also format like #2, personally. I think Rustfmt might even have a setting to do it this way?)
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.
If you do have a style you prefer, you can always configure your rustfmt to follow that style.
> 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
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.
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) :)
Instead, make clippy an optional feature, and compile with `cargo build --features=clippy` on your CI on nightly.
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 :)
And clippy uses a _lot_ of internals, so even if a plugin interface stabilizes there's no guarantee clippy will.
And HIR isn't nearly enough for clippy to work on stable.
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.
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.
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)
However I liked rust more, for what I'm doing.
apm install language-rust racer build build-cargo
But you'll have to download Racer and Rust's source code, too.
VS is easily confused by typedefs and forward declarations, in my experience.
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.
> 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...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.
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.