{
  "path": "/elegant-apis-in-rust.html",
  "site": "at://did:plc:x67qh7v3fd7znbdhauc45ng3/site.standard.publication/3mjcd2t6afe25",
  "$type": "site.standard.document",
  "title": "Elegant Library APIs in Rust",
  "updatedAt": "2016-07-21T00:00:00.000Z",
  "publishedAt": "2016-07-21T00:00:00.000Z",
  "textContent": "The existence of libraries with nice, user-friendly interfaces is one of the most important factors when choosing a programming language. Here are some tips on how to write libraries with nice APIs in Rust. (Many of the points also apply to other languages.)\n\n[You can also watch my talk at Rustfest 2017 about this!][rustfest-talk]\n\n[rustfest-talk]: https://www.youtube.com/watch?v=0zOg8_B71gE\n\nUpdate 2017-04-27: Since writing that post,\n@brson of the Rust Libs Team has published a pretty comprehensive\nRust API Guidelines\ndocument that includes my advice here and a lot more.\n\nUpdate 2020-06-01: This post is quite a few years old by now!\nMost of the patterns are still valid and actively used in Rust today,\nbut the language has also evolved quite a bit and enabled new patterns\nthat are not discussed here.\nI've updated some of syntax and crate recommendations\nbut otherwise kept the post as it was in 2016.\n\nWhat makes an API elegant?\n\n- Code using the API is easily readable thanks to obvious, self-explanatory method names.\n- Guessable method names also help when using the API, so there is less need to read documentation.\n- Everything has at least some documentation and a small code example.\n- Users of the API need to write little boilerplate code to use it, as\n    - methods accept a wide range of valid input types (where conversions are obvious), and\n    - shortcuts to get the 'usual' stuff done quickly are available.\n- Types are cleverly used to prevent logic errors, but don't get in your way too much.\n- Returned errors are useful, and panic cases are clearly documented.\n\nTechniques\n\nConsistent names\n\nThere are a few Rust RFCs that describe the naming scheme of the standard library. You should follow them to make your library's API feel familiar for users.\n\n- [RFC 199] explains that you should use mut, move, or ref as suffixes to differentiate methods based on the mutability of their parameters.\n- [RFC 344] defines some interesting conventions, like\n\t- how to refer to types in method names (e.g., &mut [T] becomes mut_slice, and mut T becomes mut_ptr),\n\t- how to call methods that return iterators,\n\t- that getters methods should be called field_name while setter methods should be set_field_name,\n\t- and how to name traits: \"Prefer (transitive) verbs, nouns, and then adjectives; avoid grammatical suffixes (like able)\", but also \"if there is a single method that is the dominant functionality of the trait, consider using the same name for the trait itself\".\n- [RFC 430] describes some general casing conventions (_tl;dr_ CamelCase for type-level stuff, snake_case for value-level stuff).\n- [RFC 445] wants you to add an Ext suffix to extension traits.\n\n[RFC 199]: https://github.com/rust-lang/rfcs/blob/1f5d3a9512ba08390a2226aa71a5fe9e277954fb/text/0199-ownership-variants.md\n[RFC 344]: https://github.com/rust-lang/rfcs/blob/1f5d3a9512ba08390a2226aa71a5fe9e277954fb/text/0344-conventions-galore.md\n[RFC 430]: https://github.com/rust-lang/rfcs/blob/1f5d3a9512ba08390a2226aa71a5fe9e277954fb/text/0430-finalizing-naming-conventions.md\n[RFC 445]: https://github.com/rust-lang/rfcs/blob/1f5d3a9512ba08390a2226aa71a5fe9e277954fb/text/0445-extension-trait-conventions.md\n\nMore method name conventions\n\nIn addition to what [RFC 199] and [RFC 344] (see above) define, there are a few more conventions around what method names to choose, which seem to not be represented in RFCs (yet). These are mostly documented in the [old Rust style guidelines][naming-conversions] and in [@llogiq]'s post [Rustic Bits] as well as [clippy's][clippy] [wrong_self_convention] lint. Here's a summary:\n\n[naming-conversions]: https://doc.rust-lang.org/1.12.0/style/style/naming/conversions.html\n[@llogiq]: https://twitter.com/llogiq\n[Rustic Bits]: https://llogiq.github.io/2016/02/11/rustic.html\n[clippy]: https://github.com/Manishearth/rust-clippy\n[wrong_self_convention]: https://github.com/Manishearth/rust-clippy/blob/55e67bfc105ef6abf0997584e0e84cc939f35dc6/clippy_lints/src/methods.rs#L88-L110\n\nMethod name  | Parameters           | Notes   | Examples\n-------------|----------------------|---------|----------\nnew        | no self, usually ≥ 1 | Constructor, also cf. [Default] | Box::new, std::net::Ipv4Addr::new\nwith_...   | no self, ≥ 1         | Alternative constructors | Vec::with_capacity, regex::Regex::with_size_limit\nfrom_...   | 1                    | cf. [conversion traits] | String::from_utf8_lossy\nas_...     | &self              | Free conversion, gives a view into data | str::as_bytes, uuid::Uuid::as_bytes\nto_...     | &self              | Expensive conversion | str::to_string, std::path::Path::to_str\ninto_...   | self (consumes)  | Potentially expensive conversion, cf. [conversion traits] | std::fs::File::into_raw_fd\nis_...     | &self (or none)    | Should probably return a bool | slice::is_empty, Result::is_ok, std::path::Path::is_file\nhas_...    | &self (or none)    | Should probably return a bool | regex_syntax::Expr::has_bytes\n\nRegarding new: If you can construct your type without any parameters, you should implement [Default] on it, and use that instead of new. An exception to this is new on \"container\" types, like Vec or HashMap, where it makes sense to initialize an empty container.\n\n[conversion traits]: #use-conversion-traits\n\nDoc tests\n\nWrite documentation with example code showing how to use your API and get automatic tests for free – Two birds, one stone. You can read more about in the documentation chapter of the official book.\n\nrust\n/// assert_eq!(min( 0,   14),    0);\n/// assert_eq!(min( 0, -127), -127);\n/// assert_eq!(min(42,  666),   42);\n/// \n\nTo enforce that every public API item is documented, use #![deny(missing_docs)]. You might also be interested in my post suggesting conventions for formatting Rust documentation.\n\nDon't \"stringly type\" your API\n\nComing from dynamically typed languages, you might be tempted to use strings with specific values in various places.\n\nFor example: You want a function to print some input text in a color, so you use a parameter color of type &str. That also means you expect your users to write one of the names from a limited number of color names (like [\"red\", \"green\", \"blue\", \"light golden rod yellow\"]). \n\nThis is not what you should do in Rust! If you know _all_ possible variants beforehand, use an enum. This way, you don't need to parse/pattern match the string -- and deal with possible errors -- but can be sure that a user of your API can only ever supply valid inputs[^illegal-states].\n\n[^illegal-states]: There is a slogan of \"making illegal states unrepresentable\" in other strongly typed languages. While I first heard this when talking to people about Haskell, it is also the title of this article by Ffor fun and profit, and this talk by Richard Feldman presented at elm-conf 2016.\n\nA module full of constants\n\nAlternatively, if you have a more complex value you want to express you can define a new struct and then define a bunch of constants with common values. If you put the constants into a public module, your users can access them using similar syntax as when using an enum variant.\n\nActually parsing a string with FromStr\n\nThere may still be cases where users of your API actually have strings, e.g. from reading environment variables or by taking their user input -- i.e., they didn't write (static) strings themselves in their code to give to your API (which is what we try to prevent). In those cases it makes sense to have a look at what the [FromStr] trait can give you, which abstracts over the concept of parsing a string into a Rust data type.\n\n[FromStr]: https://doc.rust-lang.org/std/str/trait.FromStr.html\n\nIf all you want to do is map a string with an enum variant name to the right enum variant, you can adapt this macro (from this tweet; there might also be a crate for that).\n\nDepending on your API, you could also decide to have your users deal with parsing the string. If you supply the right types and implementations, it shouldn't be difficult (but needs to be documented nonetheless).\n\nError handling\n\nThe official book has an awesome chapter on error handling.\n\nThere are a few crates to reduce the boilerplate needed for good error handling,\ne.g., anyhow (dynamic error type with methods for annotating and chaining errors),\nand thiserror (makes creating custom error types easy).\n\nPublic type aliases\n\nIf your internal code uses generic types with the same type parameters over and over again, it makes sense to use a type alias. If you also expose those types to your users, you should expose (and document) the type alias as well.\n\nA common case where this is used is Result<T, E> types, where the error case (E) is fixed. For example, [std::io::Result<T>][io-result] is an alias for Result<T, std::io::Error>, [std::fmt::Result][fmt-result] is an alias for Result<(), std::fmt::Error>, and [serde_json::error::Result<T>][serde-json-result] is an alias for Result<T, serde_json::error::Error>.\n\n[io-result]: https://doc.rust-lang.org/std/io/type.Result.html\n[fmt-result]: https://doc.rust-lang.org/std/fmt/type.Result.html\n[serde-json-result]: https://github.com/serde-rs/json/blob/e5f9ca89c6de1a7bf86aff0283bcd83845b05576/json/src/error.rs#L258\n\nUse conversion traits\n\nIt's good practice to never have &String or &Vec<T> as input parameters and instead use &str and &[T] as they allow more types to be passed in. (Basically, everything that derefs to a (string) slice).\n\nWe can apply the same idea at a more abstract level: Instead of using concrete types for input parameters, try to use generics with precise constraints. The downside of this is that the documentation will be less readable as it will be full of generics with complex constraints!\n\nstd::convert has some goodies for that:\n\n- AsMut: A cheap, mutable reference-to-mutable reference conversion.\n- AsRef: A cheap, reference-to-reference conversion.\n- From: Construct Self via a conversion.\n- Into: A conversion that consumes self, which may or may not be expensive.\n- TryFro",
  "canonicalUrl": "https://deterministic.space/elegant-apis-in-rust.html"
}