Provide a feature to turn off an interface in a non-breaking change

Suppose I have a decoder crate whose main interface mentions std::io::Read as a trait bound. Now, the main goal is to make this crate #[no_std] compatible, while avoiding a breaking change. This is obviously not possible while depending on todays io module, so the interfaces can not be enabled in that environment. Let's say this is fine and an alternative interface exists that would be no_std compatible.

struct Decode<R> {
    reader: R,
}

impl<R> Decode<R> {
    /// This can't exist in `no_std`!
    pub fn read(&mut self) -> std::io::Result<Vec<u8>>
        where R: std::io::Read {}
}
impl Decode<&'_ [u8]> {
    /// This can stay.
    pub fn read_from_slice(&mut self) -> Vec<u8> {}
}

Is it actually possible to do this? I've examine the following options already:

  • Introduce a new feature std and mark the first interface to be enabled only when that feature is active. Unfortunately, this is a breaking change. A downstream crate may have added default-features = "false" to their dependency configuration in which case they'd no longer have access to the std interface.

  • Introduce a feature no-std and deactive the interface when this feature appears. Unfortunately this is incompatible with cargo's model for compiling crates. When multiple crates have the same dependency then their features are combined. Since no-std would appear in this union it would remove the std-based read interface for crates that have not activated the no-std feature.

I don't think you can do this without breaking downstream crates with default-features = "false".

3 Likes

Yes. I'm asking if there are clever alternatives.

I'd generally read @alice's message as an assurance that no such clever alternatives exist. I have to agree with this and reiterate it. Cargo as a versioning tool is fairly simple in a number of ways, and there just... aren't any more complicated or tricky way to switch on/off features which would make this work.

I would argue the correct thing to do is to make the breaking change. If you're at a sub-1.0 version, it's expected that you'll occasionally have to break the interface when you discover something new that the crate isn't doing.

If it's a 1.0+ version, well, this is why most crates don't go to 1.0 until multiple design iterations, and a year or two to ensure there isn't anything missing. If there is, like no_std compatibility, then I'd recommend searching around for any other similar breaking changes you want to make, and going to a 2.0 version.

1 Like

Thanks you, it just wasn't clear. This was only partially about an existing crate and thankfully not a 1.0 version, but a few of them. In any case, I shall introduce features alloc and std that disable all current API and gradually provide the methods in the next major versions.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.