rustrust-cargorust-crates

How to prepare a crate for cargo publish when it needs to be built with the nightly rust toolchain?


I have extracted some functionality into a crate that I think would also be useful to others, so i would like to publish it on crates.io. However, my code makes use of some features that are not in stable rust. How do I correctly package this crate?

I would like to explicitly state what files to include, as explained in https://doc.rust-lang.org/cargo/reference/publishing.html :

[package]
# ...
include = [
    "**/*.rs",
    "Cargo.toml",
]

Should I add the rust-toolchain.toml file to the includes? It looks like this:

[toolchain]
channel = "nightly"

Or is there a better way, e.g. to specify exactly which features from nightly the crate needs? After all, it's possible that some future nightly versions will not support those features anymore.

Or maybe the #![feature(...)]) annotations in src/lib.rs are already sufficient for this?

Or maybe the rust-version field in the Cargo.toml manifest should be used for this?

All of these uncertainties boil down to the question in the title: How to prepare a crate for cargo publish when it needs to be built with the nightly rust toolchain?


Solution

  • One way is to use Feature Gates.
    (This only makes sense if there is some basic functionality that works in stable).

    [features]
    # Defines a feature named `nightly` that does not
    # enable any other features.
    nightly = []
    

    Then we can annotate code with #[cfg(feature = "nightly")] like this:

    #[cfg(feature = "nightly")]
    impl std::iter::Step for TileIndex {
    ...
    }
    

    This code will only be included if the feature flag is set by the consumer. That is, they need to either use cargo build --features nightly or specify the dependency in their own Cargo.toml with the feature flag:

    derive_more = { version = "1", features = ["nightly"] }
    

    This makes it possible to ship the crate with some functionality that works in stable rust, and optionally make the rest of the functions available.

    The #[cfg(feature = "nightly")] annotation can also be used before functions, or before code blocks within a function. However, I ran into one such case where the skipped code would cause syntax errors (because I used yield there, which is not yet stable). In that case, the crate cfg_if helps:

    cfg_if::cfg_if! {if #[cfg(feature = "nightly")] {
    impl IntoIterator for MovementRange {
    ...
    }
    }
    

    In either case, this only skips including the code, but it would still not work with cargo +stable build, because the #![feature(...)] annotations may only be used in nightly. To skip them too, you can use cfg_attr which does the same thing as cfg, but for attributes:

    // We have code that uses the nightly toolchain. It is gated behind the "nightly" crate feature flag.
    // This means the crate can still be used with stable rust - it will just not have all functions.
    #![cfg_attr(feature = "nightly", feature(step_trait))]
    #![cfg_attr(feature = "nightly", feature(coroutines))]
    #![cfg_attr(feature = "nightly", feature(iter_from_coroutine))]
    #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
    

    To test where such an attribute was used, you can first set it to conditional compilation like I just showed, and then build with cargo +nightly build, which does not automatically contain the feature flag "nightly" and will tell you what code would need the feature, so you can go and exclude it with #[cfg( feature = "nightly")].

    I don't know whether this is a good way. But after these changes: