https://blog.wnut.pw/2020/03/12/crate-publishing-guidelines-and-tips/
Unfortunately
unsafe
is still needed in rust today
This sounds like an expectation that unsafe will at some point no longer be needed, but it won't just go away.
That's a fair point. I think there was some wishful thinking on my part there, so I rephrased it. I am confident we will still see a fair reduction of the cases where it's needed though.
I wasn't necessarily thinking of special environments like no_std (device driver authors weren't exactly the target audience), but rather of how much it's around today in general purpose libs, networking code, ... often for some (hypothetical) performance gains.
This is a really nice checklist! Thank you for writing about it, I especially agree with the part about doing your own chores and the emphasis that writing a good crate is more than just cobbling together code, it's also about the documentation, tests, dependencies, etc.
I find it funny that in one section you tell people to always run rustfmt
, then the first code snippet in the next section contains code that doesn't follow standard rustfmt
conventions (note the trailing commas, space before warn
and open parens on a newline)
#![ warn
(
anonymous_parameters ,
missing_copy_implementations ,
missing_debug_implementations ,
missing_docs ,
nonstandard_style ,
rust_2018_idioms ,
single_use_lifetimes ,
trivial_casts ,
trivial_numeric_casts ,
unreachable_pub ,
unused_extern_crates ,
unused_qualifications ,
variant_size_differences ,
)]
Notably if I remember correctly, you always have to run cargo commands in the top level of the workspace, since all build artifacts and
Cargo.lock
are shared.
You shouldn't need to do this.
If you run cargo build
while in one sub-crate it'll compile everything needed by that crate and place the artefacts in your top-level target/
directory. If you run cargo build --workpace
from a subdirectory it'll compile everything in the workspace.
Thanks for the feedback. If you look carefully, I never wrote that people should always run rustfmt. I just say: "Before making code public, you might want to think about code formatting."
I personally don't use rustfmt because it can't format code in a style that I like. However, there is a merit in the possibility to format code automatically rather than manually, so I felt I had to mention that in the checklist.
Code formatting is also a very much flamewar inducing subject, so I think I didn't want to go into my personal opinions on the matter in that post.
So are you saying you can compile one crate in a workspace when others in that workspace currently don't compile?
Yep.
$ mkdir /tmp/dummy-workspace && cd /tmp/dummy-workspace
$ cargo new --lib base-lib
$ cargo new --lib some-binary
$ cargo new --lib broken-lib
$ cd some-binary && cargo add ../base-lib && cd ..
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding base-lib (unknown version) to dependencies
$ cd broken-lib && cargo add ../base-lib && cd ..
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding base-lib (unknown version) to dependencies
$ tree -I target
.
βββ base-lib
β βββ Cargo.toml
β βββ src
β βββ lib.rs
βββ broken-lib
β βββ Cargo.toml
β βββ src
β βββ lib.rs
βββ Cargo.lock
βββ Cargo.toml
βββ some-binary
βββ Cargo.toml
βββ src
βββ main.rs
6 directories, 8 files
$ cat broken-lib/src/lib.rs
this does not compile
$ cargo build
Compiling base-lib v0.1.0 (/tmp/dummy-workspace/base-lib)
Finished dev [unoptimized + debuginfo] target(s) in 0.12s
$ cd ../some-binary
$ cargo build
Compiling base-lib v0.1.0 (/tmp/dummy-workspace/base-lib)
Compiling some-binary v0.1.0 (/tmp/dummy-workspace/some-binary)
Finished dev [unoptimized + debuginfo] target(s) in 0.23s
$ cargo build
Compiling base-lib v0.1.0 (/tmp/dummy-workspace/base-lib)
Compiling broken-lib v0.1.0 (/tmp/dummy-workspace/broken-lib)
error: expected one of `!` or `::`, found `does`
--> broken-lib/src/lib.rs:1:6
|
1 | this does not compile
| ^^^^ expected one of `!` or `::`
error: aborting due to previous error
error: could not compile `broken-lib`.
To learn more, run the command again with --verbose.
Thanks a lot for clarifying. I have updated the post to remove that part. I'd have to properly play around with workspaces again to say anything meaningful about them.
Awesome article, I think the ecosystem really needed one like this one!
Trick: you can dodge it by emiting a "raw --cfg
" rather than a cargo feature:
-
Add this to the
Cargo.toml
to make it work on docs.rs[package.metadata.docs.rs] all-features = true rustdocflags = ["--cfg", "docs"]
-
Add this to the
.cargo/config
file (create it if it doesn't exist) to make it work locally:[build] rustdocflags = ["--cfg", "docs"]
-
And then the
src/lib.rs
as you suggested; I personally do something like:#![cfg_attr(docs, feature(doc_cfg, external_doc), doc(include = "../README.md"), )] #![doc = ""] // empty doc line to handle missing doc warning when the feature is missing.
-
Annotating the
cfg
-gated items with:#[cfg(feature = "example")] #[cfg_attr(docs, doc(cfg(feature = "example")))]
But this is repetitive, (plus it requires
cargo doc --all-features
locally), so a macro can come in handy:macro_rules! doc_cfg {( #[doc_cfg( $($cfg:tt)* )] $item:item ) => ( #[cfg_attr(docs, doc(cfg( $($cfg)* )))] #[cfg(any(docs, $($cfg)*))] // also include the item for documentation $item )}
to be used as:
doc_cfg! { #[doc_cfg(feature = "example")] pub struct Foo; // etc. }
or, if using
#[macro_rules_attribute]
, this can be made smoother by doing:/// At the top level: `src/lib.rs` #[macro_use] extern crate macro_rules_attribute;
#[macro_rules_attribute(doc_cfg!)] #[doc_cfg(feature = "example")] pub struct Foo; // etc.
This, of course, is to have a general tool for any feature / combination of
cfg
s.
But usually a crate has one or two recurring cfgs, such as#[cfg(feature = "std")]
, for which a more specialized macro is simpler and can handle multiple items at once:macro_rules! cfg_std {( $( $item:item )* ) => ( $( #[cfg(any(docs, feature = "std"))] #[cfg_attr(docs, doc(cfg(feature = "std")))] $item )* )} cfg_std! { pub struct Foo { ... } impl Foo { ... } }
Thanks a lot for some great tips. I will update the post or link here.
I am trying out passing the cfg docs directly, but it doesn't seem to solve the problem:
error[E0554]: `#![feature]` may not be used on the stable release channel
--> src/lib.rs:3:21
|
3 | #![ cfg_attr( docs, feature(doc_cfg, external_doc) ) ]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The problem is not cfg_attr( feature = "docs"
, it is feature(doc_cfg, external_doc)
.
As far as I can tell this is a stable to stable regression, that if I'm not mistaken started appearing with stable 1.41. Before that this compiled fine.
It doesn't seem to be reported. I haven't come round to investigating and reporting it and anyways the damage is done I'm afraid.
I should also double check if doc_cfg
is unstable in rustdoc, which might also not work on stable, but I saw my CI starting to break with just external_doc
which definitely worked before.
ps: I just found this issue which seems to describe the behavior that no longer works since 1.41:
The fact that it requires nightly
to build the docs is expected, but does cargo +stable build
not work on 1.41.0?
oh yes, if you don't enable the feature, it should work fine. Before though cargo doc did work on stable.
Yh I guess I did not explain the purpose of my changes well enough: for me having to use nightly
for the docs is perfectly fine, as it is a "best effort run once" that downstream users do not depend on kind of thing. What I personally find annoying is having to do cargo doc --all-features
, which my suggestion "solves" by making all-features
be "implicit" when running cargo doc
(thanks to the --cfg docs
always run and the #[cfg(any(docs, ...)))]
tweak). If, on the other hand, would like to have cargo +stable doc
work, then my suggestion is indeed of no good to you.
If you want your project to be smart enough to enable features when on +nightly
and not on +stable
, you could try https://github.com/dtolnay/rustversion
Yes, sorry for the confusion. It definitely makes --all-features
work on stable again.
Found one more way this breaks. cargo test
when running doctests will enable the rustdocflags
, so cargo test no longer works on stable.
Only workaround I see is to disable the features by using all( not(doctest), doc )
and run the doc tests specifically in nightly.
Super helpful your guide.
One little note about the changelog, you could think about conventional commits and a generated CHANGELOG.md via the rust tool jilu.
At least I find it very handy.
Thanks a lot for your great efforts.
Hi, thanks a lot and welcome to the user forum. Yes, automated changelog was on my todo list. I was going to look into projects like wasm-bindgen to see how they did it.
In the mean time I'm still busy rounding up issues with external docs... It's actually more complicated than I would hope and maybe describing it will be to long to fit in the post inline, but I'll keep updating the information with the best practices I can find, so thanks for sharing jilu. I will look into it.
Using .cargo/config
to enable the docs
flag locally is interesting, but as mentioned it breaks using non-nightly compilers. As an alternative I just have nightly rustdoc features tested on a nightly CI build using RUSTDOCFLAGS='--cfg=docsrs -Dwarnings' cargo doc --all-features --no-deps
, and if I'm changing something relevant and want to see what it looks like I just manually run such a command locally.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.