Broken build from someone's Edition 2021 change

Hey. On an embedded Linux system, I'm currently stuck with the v1.54 Rust compiler (thus Edition 2018), and using serde_json. My build broke a few months back, and it seems that someone down the dependency chain switched their crate to Edition 2021 within the same semver major version of the crate. So I can't seem to change my specific dependency to fix it.

But I'm not even sure who to blame and/or ask for help.
Is it OK to update an compiler Edition within a major version? Shouldn't that be a breaking change?

Or is it the responsibility of the next package up the chain to fix it?

Or, either way, is there any way I can fix it in my own crate to resolve the problem?

Thanks

2 Likes

This problem is not strictly about editions, as any new compiler requirement can cause trouble if you're unable to upgrade your toolchain. Cargo guidelines call this a "possibly-breaking" change:

https://doc.rust-lang.org/cargo/reference/semver.html#env-new-rust

I have personally taken the stance to allow such changes, but I do it in a minor version bump at least. That way folks can pin to the prior foo = "~x.y", and I can still publish a patch update to that series if needed. But I would only recommend such pinning at the application level -- if you're a library crate, you shouldn't force your users to stay on old versions when they do have a newer compiler.

A crate author also has to make some decision of how soon to allow a version change, or how old of a compiler to continue supporting. Some authors are as aggressive as N-1 or N-2 from the current release! I tend to go for about 6 months to a year, but for other wide-reaching crates I've been much more conservative, supporting years-old compilers. My autocfg crate still supports Rust 1.0! :slight_smile:

1 Like

I guess I understand, but it still seems so... unsatisfying.

The idea that a package that you never heard of and is buried in the dependence tree can break your build even though you didn't change anything seems to defy the idea of semantic versioning.

But I also tend to work in the Embedded Linux space, and might get locked into specific compiler version for a year or more, so ai tend to be more sensitive to the issue. Upgrading can require the buy-in of multiple groups at a company, and require complete validation process. In our case, we also use yocto as the build system and are also limited by the latest compiler version supported by the meta-rust package. So, even though 1.65 will likely be out by the time we can upgrade, we're likely only going to be able to bump our build to 1.59.

Hopefully MSRV will help with this: RFC: Minimum Supported Rust Version by newpavlov · Pull Request #2495 · rust-lang/rfcs · GitHub

Because then hopefully those crates will have it specified accurately, and those newer versions won't get picked up on incompatible toolchains.

1 Like

Lots of related discussion here and here.

1 Like

This shouldn't happen if you check your Cargo.lock file into version control.

The only time cargo will choose to use a newer version of a dependency is when it needs to resolve dependencies from scratch (i.e. because Cargo.lock doesn't exist) or if you run cargo update to update Cargo.lock to use the newest semver-compatible version of each dependency.

7 Likes

You can downgrade this dependency with:

cargo update -p $BAD_DEPENDENCY --precise $GOOD_VERSION

lib.rs shows compatibility for each version, example.

But I suggest just using rustup and keeping compiler at the latest version. Newest Rust compilers are way more compatible with old dependencies than the dependencies are with old compilers.

3 Likes

Exactly -- your build breaking shows that you did change something -- you upgraded a dependency somewhere.

Yes. The code works for “me”, but the problem pops up when the production automated build system loads a fresh copy of the build branch from git or the SQA team gets a ticket to grab a new branch and start testing.

This is in a library, so I was following cargo guidelines to not add the lock file to git. But SQA is required to load a fresh copy of the branch, then build, and run the unit tests on the embedded target board before it can be merged with master.

But, I’m guessing in this case of a closed (non-public) library, adding the Cargo.lock file to git is still the right way to go.

Cargo.lock of a library will be ignored either way. So unfortunately every user of this library will have to either use a supported version of Rust, or do the same downgrade dance individually to make their Cargo.lock.

Yes, apologies, it turns out that my problem is perhaps a bit subtle, and not what I originally thought.

I understand that the library's Cargo.lock will be ignored by an application that uses the library. So each application needs its own Cargo.lock. (In my case all the apps do keep their lock files in git and build fine).

But, in my case, the library itself must pass its own unit and integration tests before it can be accepted and merged with master.

So essentially before an update can be merged, an SQA person does:

$ git clone mylib
$ cd mylib
$ cargo test

Currently that won't build because some packages are resolving to Edition 2021 (or any new compiler that we use). I'm assuming a Cargo.lock file with the library would fix this?

@cuviper 's suggestion to pin a package works, but more and more are switching to 2021, so it's only a temporary solution.

I think the origin of the suggestions to not check in Cargo.lock for a library come from the fact that it only applies to unit tests, but that's exactly what you want here!

(Whereas, for most libraries, they want to be continually upgrading to new dependency versions to expose new test breakages ASAP.)

So I say you have a very good reason to do the opposite of the advice. Even better, though, would be sharing a Cargo workspace with your application, so you test with the versions you will deploy.

1 Like