Hugo Hacker News

Rust API Guidelines Checklist

AaronFriel 2021-08-18 17:28:58 +0000 UTC [ - ]

These are the guidelines that the Rust language standard library holds itself too, and tend to be good advice for authors of libraries.

By no means should anyone be intimidated by this. This list is, well, something like a code review guide similar to what you'd find to make changes to Postgres or Python. New APIs that are part of the standard library need to be be consistent, performant, debuggable, documented, and so on.

Following this list for a hobby, side project, or for the 0.1.0 release of a new library is entirely unnecessary.

rdpintqogeogsaa 2021-08-18 18:35:07 +0000 UTC [ - ]

> Following this list for a hobby, side project, or for the 0.1.0 release of a new library is entirely unnecessary.

This may be a hot take, but I'll go and take my chances.

Following this list for a hobby or side project or even an initial release of a library is a very good idea. It's hard to tell what kinds of libraries will or won't catch on. As soon as you have downstream users, they will get annoyed by churn. If given a checklist, it seems reasonable to me to expect people to actually follow it out of obligation to your future downstream consumers of the library.

Not only that, but I believe that most of the things on that list boils down to good engineering. Following good practice early on even just in a hobby/side project will be rewarded as the project grows.

By releasing a library (even as a 0.1.0), you imply that you expect it to be at least usable for some kind of usage. Releasing known-bad code is something nobody would do, especially since every major Git repostory hoster lets you have private repositories.

tialaramex 2021-08-18 19:43:08 +0000 UTC [ - ]

> Releasing known-bad code is something nobody would do

Mmm. I guess it depends how mischievous people are.

Is https://crates.io/crates/misfortunate known-bad code? Take misfortunate::Comte which claims to be an ExactSizeIterator but lies and with a simple "tap" from your wand produces an infinite number of cloned Rabbits ?

OK, how about Mara's https://crates.io/crates/whichever-compiles or https://crates.io/crates/nightly-crimes ?

I mean, maybe I'm a bad person for writing misfortunate, but is Mara, Rust's library team lead, a bad person?

AaronFriel 2021-08-18 20:40:18 +0000 UTC [ - ]

I think the person you're replying to wasn't suggesting those are bad people, but it's hard to dispute that "misfortunate" which violates trait contracts is "bad code". It's well written, good for some definition of good, but also it will definitely break callers that rely on the invariants traits represent and by that meaning, it is "bad code".

Fortunately, these people are very clever, very good Rust developers and they are not maliciously dropping nightly crimes into widely used crates :)

seoaeu 2021-08-18 18:53:03 +0000 UTC [ - ]

Sure you shouldn't go out of your way to violate the suggestions on the list, but you also shouldn't be afraid to publish a project just because you haven't meticulously gone through it and checked off every item

stouset 2021-08-18 19:57:46 +0000 UTC [ - ]

100%. I haven’t read the whole list yet, but all of them I’ve seen so far are extremely trivial conventions. None of these should be difficult or time-consuming for even a novice Rustscean to follow.

codetrotter 2021-08-18 16:42:11 +0000 UTC [ - ]

About the guidelines, from https://rust-lang.github.io/api-guidelines/about.html

> This is a set of recommendations on how to design and present APIs for the Rust programming language. They are authored largely by the Rust library team, based on experiences building the Rust standard library and other crates in the Rust ecosystem.

GitHub repo for the guidelines:

https://github.com/rust-lang/api-guidelines

I am submitting the OP link to HN because I believe that these guidelines are useful to many people that have, or wish to, publish Rust crates that other people will then use.

Following these guidelines will make the crates we make feel "native to Rust" for other people that want to make use of our crates.

I found these guidelines just a couple of days ago for the first time, in spite of having written Rust for several years now. And I suspect that many others may not have read these guidelines and that others will benefit from reading them too.

Cicero22 2021-08-18 17:08:07 +0000 UTC [ - ]

Wish the checkboxes weren't disabled. You can give this a run in console to enable them all (If you trust random code):

  document.querySelectorAll('input:disabled').forEach((el) => el.disabled = false)

davidkunz 2021-08-18 17:23:52 +0000 UTC [ - ]

As someone who doesn't write Rust professionally, this list scares me a bit. Even though many items apply to other programming languages as well (and some are even handled more easily in Rust), it appears to be quite easy to forget something of great importance to some of your users. What if you forgot to derive the Debug/Display trait and someone wants to print your struct, or you forgot to implement PartialEq and someone wants to write unit tests? Maybe this doesn't really happen in practice, I would be very interested if some experienced Rustaceans could share their experience.

loa_in_ 2021-08-18 17:30:35 +0000 UTC [ - ]

This does happen in practice. Usually it is remedied by submitting an issue or a PR to your dependency upstream and getting them to fix it.

If you're really impatient you can implement the trait yourself (with some hacking involved): wrap the type in a new struct and implement the trait there.

Why hacking around like that? Rust requires at least one of: the type, or the trait; to be originated in your crate.

davidkunz 2021-08-18 20:15:08 +0000 UTC [ - ]

I just found an interesting reddit thread discussing must-have traits: https://teddit.net/r/rust/comments/p5vsjl/is_there_any_reaso...

AaronFriel 2021-08-18 17:31:56 +0000 UTC [ - ]

In cases like this, since everything is on GitHub it turns out to be pretty easy to make a PR to fix - it's usually only a few characters of code change.

API names and such are of course breaking changes, but unless you're authoring Tokio or Hyper or another huge, important crate in the ecosystem, it's really not worth it to do everything on this list.

duped 2021-08-18 17:31:18 +0000 UTC [ - ]

Not everything is printable, comparable, or equalable (#[derive(Debug PartialEq)] isn't guaranteed to work - it has a few preconditions).

So you live with it. If you draw a comparison to C++, almost none of these things are implemented for dependencies and there's no easy helper to add it.

2021-08-18 17:28:38 +0000 UTC [ - ]

Zababa 2021-08-19 05:03:46 +0000 UTC [ - ]

The part about the naming conventions (https://rust-lang.github.io/api-guidelines/naming.html#ad-ho...) reminds me of Elixir's size and length (https://hexdocs.pm/elixir/naming-conventions.html#length-and...). I wonder if there is a way to enforce those conventions with a compiler/linter?

kvark 2021-08-18 17:55:05 +0000 UTC [ - ]

The presence of this list is very helpful. Unfortunately, some of the points are controversial.

> Types eagerly implement common traits

This is particularly concerning to me for multiple reasons. If my 4K structure can be copied, should it derive `Copy`? This leads to users implicitly copying it around instead of using references more aggressively.

If all my types implement `PartialEq`, `Debug`, and other stuff, that means a lot of code generation at compile time, which gets hopefully eliminated by LLVM passes. And then we are wondering why Rust compilation/linking is so slow :/

hcs 2021-08-18 20:45:41 +0000 UTC [ - ]

There is a clippy lint, pedantic and allowed by default, that warns about passing a large type by value.

https://rust-lang.github.io/rust-clippy/master/#large_types_...

The default threshold is 256 bytes: https://github.com/rust-lang/rust-clippy/blob/1fc1aee7be6e7e...

So you might use that as a rule of thumb. But officially, "Generally speaking, if your type can implement Copy, it should." https://doc.rust-lang.org/std/marker/trait.Copy.html#when-sh...

kvark 2021-08-18 21:18:00 +0000 UTC [ - ]

That's going to be helpful to enable in WebRender/Euclid, thank you!

volta83 2021-08-18 19:09:48 +0000 UTC [ - ]

> If my 4K structure can be copied, should it derive `Copy`? This leads to users implicitly copying it around instead of using references more aggressively.

Rust eliminates unnecessary memcpys, but this is a bit up to you. `Copy` as a synonym of "cheap to copy" is used by some crates, although this is not official AFAIK.

> If all my types implement `PartialEq`, `Debug`, and other stuff, that means a lot of code generation at compile time,

If you don't use these, no LLVM-IR is even generated for these. Rust compiles code "on demand".

kvark 2021-08-18 21:16:57 +0000 UTC [ - ]

Then you are at mercy of Rust optimizer. I don't want to always think about Rust optimizer when looking at or reviewing the code. I want to see the costs explicitly in the code.

Once Copy/Debug/PartialEq are derived, it will be tempting to use them, so the compile time cost will apply.

volta83 2021-08-19 10:22:31 +0000 UTC [ - ]

These are guidelines that the standard library aims to follow. It does not blindly follow them, and they only represent the values and trade-offs that make sense for the standard library. If your projects have different values and trade-offs, just do your own thing (Rust's makes it easier than all other languages I know).

If you don't want to use a standard library that follows these, just fork it and change it. Rust makes this trivial, and projects that must do this go and just do it.

I really don't know what point you are trying to make. Implementing Copy for all types that are cheap to copy makes sense for most Rust programmers.

That does not mean that it makes sense for _you_, but if you disagree about this not making sense for most, then Rust is probably the wrong language for you, and you should consider using something else.

tialaramex 2021-08-18 22:47:04 +0000 UTC [ - ]

This is an API guide. So what you're talking about is deliberately making libraries you provide worse so that people aren't tempted to use them, that ought to be ringing some alarm bells.

tialaramex 2021-08-18 19:28:20 +0000 UTC [ - ]

If you really believe your users won't understand this is a ~4K object and might foolishly copy it when they didn't mean to, implement Clone instead so that they can have another one if that's what they really meant.

If you're exaggerating and the object is really 64 bytes, just suck it up and derive Copy, your estimate of how "expensive" it is may well be so wrong that you're defeating your own objectives.

I've never seen any indication that unneeded PartialEq or Debug for example aren't elided by the compiler. In contrast if I need Debug it sure is a pain in the backside to implement it manually in a wrapper type because the owner of the type decided unilaterally that nobody would need that.

kvark 2021-08-18 21:19:24 +0000 UTC [ - ]

I want to see the costs in the code, not imply them. Also, profiling programs in Rust often shows full of memcopies. Saying that they are nicely optimized out is dishonest.

2021-08-18 19:31:42 +0000 UTC [ - ]