Transitive dependencies breakage?

My crate depends on these 2:

ratatui = "0.24"
tui-textarea = "0.3"

tui-textarea also depends on ratatui, and the ratatui versions have to match, as I'm passing structs around.

tui-textarea specifies:

ratatui version = ">=0.23.0, <1"

This used to work, and still works on one of my machines, where version 0.24 of ratatui is pulled by both.
On some other machine however, caching might be a bit different, and I end up with errors, because versions 0.24 and 0.25 of ratatui are pulled.

Is there anything I, or these crate's maintainers did wrong ?
Either way, what's the cleanest way to fix the issue ?

First you can commit Cargo.lock so all the machines behave the same.

As for the errors - it's hard to tell since you are not showing them :slight_smile: Something pulls ratatui 0.25 and this is not your crate.

ratatui version = ">=0.23.0, <1" doesn't make much sense to me. 0.23.0 doesn't have to be compatible with 0.24.0 or later versions even if the first digit is 0..

2 Likes

Is there another way ? I don't really want to lock a particular version, I just want only one version to be pulled.

The error is:

error[E0308]: mismatched types
...
433  |         .set_block(enclosing_block(" Keyword ", state.focused == Area::Keyword));
     |          --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `ratatui::widgets::block::Block<'_>`, found `ratatui::widgets::Block<'static>`
     |          |
     |          arguments to this method are incorrect
     |
     = note: `ratatui::widgets::Block<'static>` and `ratatui::widgets::block::Block<'_>` have similar names, but are actually distinct types
note: `ratatui::widgets::Block<'static>` is defined in crate `ratatui`
...
     |
230  | pub struct Block<'a> {
     | ^^^^^^^^^^^^^^^^^^^^
note: `ratatui::widgets::block::Block<'_>` is defined in crate `ratatui`
...
     |
233  | pub struct Block<'a> {
     | ^^^^^^^^^^^^^^^^^^^^
     = note: perhaps two different versions of crate `ratatui` are being used?

It's tui-textarea pulling ratatui 0.25. Cargo shows, on the problematic machine:

ratatui v0.24.0
โ””โ”€โ”€ mycrate
        
ratatui v0.25.0
โ””โ”€โ”€ tui-textarea v0.3.1
    โ””โ”€โ”€ mycrate

And on the good/old one:

ratatui v0.24.0
โ”œโ”€โ”€ mycrate
โ””โ”€โ”€ tui-textarea v0.3.1
    โ””โ”€โ”€ mycrate

ratatui version = ">=0.23.0, <1" is written by the authors of ratatui, so maybe there's something wrong, but it seems to me that this is similar to "0.23", which a lot of people use.

It really isn't. A version of "0.23" effectively means "any version which is semver compatible with 0.23". ">=0.23, <1" means "any version at all inbetween these two, irrespective of compatibility". I don't think Cargo handles ranges like this very well, possibly because almost no one uses them like this.

In case you aren't aware (and I suspect whomever wrote that version spec wasn't), 0.23 and 0.24 are as incompatible as 23.0 and 24.0. Leading zeroes "don't count" toward determining compatibility. The only reason to use ranges like this is when, for example, 0.23.8 or higher is broken, so you write ">=0.23.0, <0.23.8" until the problem is fixed.

6 Likes

You want to make sure that program compiled from source now behaves the same when compiled next year. Your question is one example of what can happen. Here's one more.

It doesn't make much sense from how cargo handles semver, that's it.

2 Likes

My crate is a library. I thought the general recommendation was to ignore the lockfile for libraries, and commit it for binaries.

I don't necessarily want to keep the exact same behaviour, but some compilation errors popping when I don't change anything is annoying.

This was recently changed

Your crate is a library - the only place you compile anything is in your tests, for tests you just use Cargo.lock file you commit.

As for tui-textarea - you are not the only one having similar problem: trying to use tui-textarea in another project - version conflict ยท Issue #55 ยท rhysd/tui-textarea ยท GitHub

1 Like

This crate is not just compiled for its unit tests, but also by any of its dependents. In this precise case, if that matters, it's build for a build script of a dependent.

Forcing my whole ecosystem to lock everything is a bit extreme, but if that's the only solution I might try. I'm still trying to understand if this is a cargo flaw, or if someone is doing something wrong somewhere.

I'm pretty sure that's tui-textarea, see my comment there. Cargo does only what you (gimme 0.23) and tta (gimme anything) ask it to do.

As for what you can do about it - I'd wait for the reply from tta maintainers, meanwhile commit the lockfile so your tests are working and explain the shenanigans in the readme: your users need to add ratatui of 0.23 version first, run cargo check, then add your crate.

2 Likes

Thanks for reproducing the issue.

From what I understand, versions 0.23 and 0.25 are not considered compatible, and this is what allows cargo to use both of them ? (otherwise, it's supposed to only use one version when one is found matching both requirements).
But at the same time, it upgrades the one pulled by tui-textarea to 0.25 when that version comes out, because of how the requirement is expressed.

It seems to me like this issue can come up whenever we have a caret or >= requirement with a 0.x version, can't it ?

Should the tui-textarea maintainer (and everyone depending on 0.x versions anywhere) express something like "~0.23" or "0.23.x" ?

Caret requirement is OK - it will not pull anything incompatible. Actually, that's the default for Cargo, i.e. just 0.23 would be correct (and e.g. =0.23 might be overly restrictive).

Isn't the issue here that caret "0.23" will pull you 0.23.0 one day, and 0.24.0 the next day, which are incompatible?

This behavior can also break tui-textarea if ratatui changes something that tta relies on so ">=0.23.0, <1" is wrong.

That would be better, but this means for every new semver incompatible release of ratatui there must must be a new release of tta and all other crates that use it.

1 Like

It is explicitly specified that caret dependencies allow for compatible changes, i.e. for 0.x it doesn't allow bumping x.

2 Likes

But >=0.23 would allow non-compatible upgrade?

It's said here:Dependency Resolution - The Cargo Book that caret is "equivalent" to >= something. But if it's actually not completely equivalent, that could be the reason for the strange requirement on the tta side.

The distinction is subtle, but ^0.23 and >=0.23, <1 differ greatly. The leading zeroes need to be ignored, as described by the chapter you linked.

Versions are considered compatible if their left-most non-zero major/minor/patch component is the same.

For instance, it should be clear why ^23 and >=23, <* are different. This is precisely why >=0.23, <1 is a bad version specifier.

It is. 0.23 is equivalent to ^0.23, and ^0.23 is equivalent to >=0.23.0, <0.24.0.


Another thing not yet mentioned is that there is a valid use for specifying a range including multiple major[1] versions: If the library has already released several major versions and you know your package is compatible with all of them, then you can write >=0.23, <0.26 to let your package work with them. The mistake in tui-textarea is claiming compatibility with not-yet-released major versions.


  1. by Cargo's definition, which ignores leading zeroes โ†ฉ๏ธŽ

6 Likes

Interestingly enough it won't help in current situation and we'll need to find something else to blame :slight_smile:

Next one I'll probably target cargo for installing two different versions even when there's a single one that satisfies both. Or Cargo.toml that doesn't let you to specify "I'm using ratatui 0.23 so tta should use the same version". Ability to specify or override the dependencies for your dependencies should help here. Nix flakes let you do that for example.

Is this issue at play here ? Major-open semver range does not properly unify with closed semver ranges ยท Issue #9029 ยท rust-lang/cargo ยท GitHub

Yea, looks like it.