Teaching libraries through good documentation
How do you teach people how to use a complex framework or library1? Here’s what comes to mind: Tutorials (guides), API documentation, great error messages, or forums/chats. Maybe video tutorials, and books, too. Since guides are one of the most effective ways to get people started, I’ve been musing about them for a while.
Guides
Recently, Sergio Benitez released the Rocket web framework for Rust. There are other frameworks/libraries for writing web application servers, but what really sets Rocket apart is the very nice API2 and documentation thereof. Reading the guide, it looks quite easy to write a simple application (there is a guide to write a pastebin that takes about 15 min to follow).
This is in stark contrast to iron, a more minimalistic and very modular framework/set of libraries that allow you to build middleware-based web applications. This means that for each aspect you want to add to your application (routing, cookies, etc.), you include another library and compose them all together. Getting started with iron up to the point that you can build a pastbin with it, is a lot harder. Obviously, one could write a guide on how to write that with iron (and maybe someone already did), but you still need to familiarize yourself with the design principles of iron, what modules are available, and understand how they compose. Rocket gives you that in one opinionated package and you can just start writing code.
I’m more skeptical when it comes to more complex things that the guide does not cover, though. Not just in Rocket, but in most other complex libraries/frameworks as well. Because, at some point, you need to leave the nicely written guides and tutorials behind and dive into the raw and unvarnished truth that is the API documentation3. And nobody told you how to find anything in there.
I try to help out people who come to diesel’s Gitter channel. A lot of questions are on how to get into more difficult stuff, after having read the guide (like Rocket, diesel has a guide on how to write a sample application). Most people are quite clever, know what they want, and see that it’s quite likely that all parts are already there. But it’s still hard to find this in the API documentation, because the library is very generic and it’s not always obvious how things work together.
Some tricky cases (with answers):
-
How do you add multiple predicates to a
where
clause?Calling
.filter
twiceAND
s both predicates together. (You can find it in the description ofFilterDsl
.) -
I saw
.or
used in an example. What can I use it with?You can call
.or
on everything that:- can be turned into an expression, and where
- that expression is boolean.
You can see this by searching for
or
, findBoolExpressionMethods
, and then look at whatBoolExpressionMethods
is implemented on:impl<T: Expression<SqlType=Bool>> BoolExpressionMethods for T
(and guessing whatExpression
means).
Luckily, a lot of these cases “just work” as I would expect them to work. That’s nice, but you shouldn’t rely on that. This is why I ask people if they want to write some of what they discovered/learnt/accomplished down as a short blog post/tutorial/guide/etc. Here are a few things I have on my “maybe write a tutorial on that” list:
- How to use associations
- Using diesel with a more complex schema (e.g., we recently introduced composite primary keys)
- Using custom data types (e.g., making
ToSql
andFromSql
work for aPoint
struct) - A “larger roundtrip”: Like the current “Getting Started” guide, but aside from defining a schema and doing some CRUD, also add validation, (de)serialization, and great error handling.
Guides are integration tests
(This section was added after a talking to @fasiha. Thank you for the inspiration!)
Thinking more about it, I had an epiphany: Guides are like integration tests! In fact:
Unit tests | Integration tests |
API documentation | Guides and tutorials |
Singular aspects | The library as a whole |
So, let’s make writing guides as easy as writing integration tests!4
(Some part of the usual API docs, like top-level crate and module documentation, may also be used to describe more general, integration-level things. I’d argue that those are just guides in disguise, though. :))
Merging two worlds
Here are some ideas on how to make these guides even better:
- Treating code snippets in guides as doc tests to make sure the examples in the guide always work
- Easy/automatic linking to the API docs
- In the prose when mentioning a struct/trait/macro
- In code examples when hovering over an item
- Easily turn guides into example projects and vice versa: A lot of libraries already include an
examples/
directory with code we can build guides on. We can e.g. use literate programming5, if we need to manage to- somehow embed all necessary meta data files (like
Cargo.toml
), - restrict the code in the guide to be additive (i.e., we can’t easily write a partial implementation and then later replace that; we can however wrap code in modules like
mod try_1 { … }
andmod try_2 { … }
and so on), - and make rustdoc and tango work together nicely.
- somehow embed all necessary meta data files (like
I’m thinking about making this work with rustdoc, which already has a lot of these capabilities. Making rustdoc work as a library, or adding some features of mdBook to rustdoc would probably take as 90% of the way.
I’ve also been thinking about making API docs better/more structured by specifying some guidelines. It will also be interesting to see if enforcing some of these guidelines, like “each public method/function needs an example”, can further ensure that all parts of an API are documented and easy to grasp.
Jimmy Cuadra wrote an RFC on the topic, but it was closed as he didn’t have to time to revise it. I would love to see a new RFC (with my suggestions); and maybe I’ll have some time in the new year to write one.
An intermediate solution
Another idea might be to write a tool that extracts code blocks from Markdown and saves them as files, thus turning a guide into a compilable project. These Markdown files can for example live in a docs/
folder so they can automatically be rendered by Github Pages. This way, you can design a beautiful landing page for your project, with guide, examples, and all you desire, but at the end of the day will still be able to turn your guide to source code that can be compiled and tested.
Assuming you want to use Jekyll (so Github renders the page for you), I have no idea how to easily omit some lines of code from the output (like lines that start with #
in rustdoc), or how to automatically add links to the API docs.
On the other hand: You can do this today! I wrote a simple proof of concept of this called waltz.
-
When does a library become a framework? When it defines your application’s structure? ↩
-
Sadly, Rocket’s API depends on unstable compiler features. I hope to see the most important ones (specialization and procedural macros) becoming stable in 2017, though. ↩
-
Oh, and I’m not talking about some weird, shabby API docs from the 1990s built on HTML3 framesets and tables. Rocket’s (as well as Diesel’s) API docs are rendered by rustdoc, which offers a pretty good search, nicely rendered descriptions, and (once you get used to it) is easy to navigate. ↩
-
Rust’s package manager Cargo makes it really easy to put a bunch of source files in a
tests/
directory and treat each of these files as an application that depends on your library. ↩ -
Basically, teach the compiler to treat markdown files as Rust code. ↩