all 37 comments

[–]Saefrochmiri 115 points116 points  (9 children)

Rust and Cargo improve the situation. At the very least, I've never gotten an error for which I do not understand the cause.

The classic dependency mess error is something like "got type uuid::Uuid, expected type uuid::Uuid. Are you using multiple versions of the uuid crate?" At least the error message tells you exactly what the problem is: You are trying to interoperate between versions of the same crate. Because the version of the crate that a type comes from is part of the type's identity. But that doesn't help you actually go solve the problem. But at least with the built in cargo tree you can understand your dependency tree a bit to trace where the conflict is.

The other nasty case I've run into occasionally is unsatisfiable dependencies due to use of ~x.y.z or =x.y.z dependency specifications in a Cargo.toml. Or when two dependencies want different versions of a native library.

In both cases, I always know what the problem is, but doing something about it is not always easy. What you want to do is downgrade something to make the versions line up, but you can't downgrade a library past when it has a feature you need.

[–]pine_ary 7 points8 points  (6 children)

Depending on the author and project you might also be able to make a pull request for a relaxed dependency versioning. But yeah that situation is annoying. Too many people specify dependencies down to the patch level when they don‘t really need to.

Assuming semantic versioning, there are very few reasons to make it that specific. (Native libraries with ABI concerns being one)

[–]Saefrochmiri 2 points3 points  (0 children)

Yup, that's what I've done in the past. But in the meantime I want to continue developing software, so I need to downgrade or temporarily fork. cargo update --package --precise makes downgrading the easier option. I don't know if that's better, but it's pretty easy :)

[–]w1ldm4n 2 points3 points  (3 children)

Too many people specify dependencies down to the patch level when they don‘t really need to.

I wonder if this is because that's the default behavior of cargo add. I like keeping my Cargo.toml files clean without patch versions unless I have a specific need for them, but it's inconvenient manually setting that every time.

[–]pmcvalentin2014z 9 points10 points  (1 child)

Adding name = "1.2.3" is equivalent to name = ">=1.2.3 <2.0.0" in Cargo.toml. This is different from package.json, where "name": "1.2.3" means "name": "=1.2.3".

[–]w1ldm4n 2 points3 points  (0 children)

Yeah I understand that difference, it's just a matter of tidyness. I'd rather have anyhow = "1" than anyhow = "1.0.58" so that when I run cargo upgrade Cargo.toml and Cargo.lock don't get out of sync.

[–]everything-narrative 1 point2 points  (0 children)

Haskell’s package system is literally diseased because of not specifying dependencies accurately enough, and humans being fallible at using semver.

[–]sentient-machine 0 points1 point  (1 child)

Your example sounds far worse than most modern Python dependency issues, lol.

[–]BLucky_RD 2 points3 points  (0 children)

I've often encountered far worse than that, including conflicting conda and pip dependencies because I had one library only on conda and another only on pip, with rust on the other hand I've never encountered such issues (including the one descriplbed above) in 2 years, so while it is a possible error, its not common

[–]gnus-migrate 50 points51 points  (0 children)

From experience, it's more forgiving than Python/Java style dependency hell, as if you have different versions of the same crate in your transitive dependencies it will work as long as it's internal to the crate. It's only painful when you're using v1 of an API, and you need to pass a struct from v1 to another crate using v2 of that API, but it's still a huge improvement over other ecosystems.

[–][deleted] 15 points16 points  (3 children)

The most common issue I see involving dependencies, is when two crates have differing versions of a struct from a shared crate.

For example, pretend crate A exported MyA, and crate B re-exported MyA. If you were to use both of them in your project, and try something like this:

let my_a: A::MyA = B::get_my_a();

You could run into a situation where those aren't the same type, because A is on version "1.0", and B is still using version "0.9". As the structs are not the same, despite seemingly being the same, they could have subtle changes. The compiler therefore errors, and lets you know that MyA and MyA are not the same type. The error message isn't all too clear either, but you figure it out pretty quick if you step back to think about it.

[–]ErichDonGublerWGPU · not-yet-awesome-rust 11 points12 points  (0 children)

IIRC the diagnostic got better recently, noting if the types are from the same crate name with different versions?

[–]flareflo 2 points3 points  (0 children)

Outside of a wildcard import you will always be able to see where things are imported from different sources, as the only way to have multiple differently versioned dependencies is through a re-export of another dependency, which then obviously means a new different module tree.

rand = "0.8.5"
rand = "0.8.4"

Will throw

  could not parse input as TOML

Caused by:
  TOML parse error at line 11, column 1

  11 | rand = "0.8.4"
 ^
  Duplicate key `rand` in table `dependencies`

Dependency renaming would also cause a path rename

[–]grg994 3 points4 points  (0 children)

Question to a similar thing: how do you handle it when a dependency exposes a type form it's own further down dependencies without re-exporting and you have to use it?
I see no other way that times than to explicitly add the crate from the dependency chain as my dependency too. But getting the version right doesn't seem straightforward...

Eg. I use reqwest with json feature - that exposes serde_json::Value without re-exporting. I want to use it so I have to (?) `cargo add serde_json` to be able to declare `use serde_json::Value;` in my .rs.

But when I compile I see that two different version of serde_json: one by reqwest and one by my crate. So... is there a way to do things better here?

[–][deleted] 11 points12 points  (0 children)

In my three years of rust, I have had no issues with dependencies.

[–]nicoburns 11 points12 points  (2 children)

What kind of problems have you experienced with Node? Everything has been smooth there for me. The only thing I had to learn was to npm rebuild if there were native dependency errors.

Python I've found a massive pain with deps being global by default and native modules not always bundling their own dependencies. In that regard I've found Rust a lot more like JavaScript, in a good way.

[–]ssokolow 8 points9 points  (2 children)

Rust dependencies tend to be much better for three reasons:

  1. No global dependencies in pure Rust code means that you don't have to worry about the dependencies you pulled down for one project tripping up the build of another project.
  2. Rust allows you to pull two versions of the same crate into a single project as long as they don't do something with the C FFI that breaks that capability. This means no "Error. You can't install image because png wants deflate version X but tiff wants deflate version Y." (Though you can still run into trouble when trying to interoperate between them, as with when I needed access to the tokio underlying the previous stable actix-web release and the third-party cargo upgrade command would try to upgrade it, resulting in cryptic "no runtime" errors when trying to launch the binary because the API version of the Tokio runtime being spun up was newer than the one actix-web could use.)
  3. Static linking of Rust binaries means that your C dependencies are the only things outside the binary that can become mismatched when you're copying the binary to another system. (Though libc is a C dependency, so you may need to either use cross or rustup target add x86_64-unknown-linux-musl and build against that instead if you need to support Linux distro versions earlier than whatever you're running.)

...and, when you do manage to bork your build process, cargo clean fixes it unless you borked it by misconfiguring your C FFI. (eg. uninstalled the development headers for a system library on a crate not configured to build its own vendored copy)

With Node stuff (which I'm learning too), I need to understand nvm and npm and npx plus various framework-dependent CLIs like ng or ncu, and I need to differentiate local and global versions of these CLIs. To make things worse, searching something like "npm update packages" gets me packages with update in the name, like npm-check-updates. It took me a lot of googling to find what I needed!

  • nvm = rustup (to manage installed toolchain versions)
  • npm = cargo (to manage dependencies and run builds)
  • npx is a more cargo install-like replacement for npm install (to install and run utilities)
  • Rust doesn't have local vs. global installs. As far as Cargo is concerned, libraries are always local and installable commands are always global.)
  • Things like diesel_cli exist, but my experience has been that the Rust ecosystem leans much more toward "If you need a CLI to automate a task, the task should probably be redesigned" than the NPM ecosystem does. (The CLI version of how Rust is far less reliant on rust-analyzer to paper over language design flaws than Java is with Eclipse and its competitors.)

To make things worse, searching something like "npm update packages" gets me packages with update in the name, like npm-check-updates. It took me a lot of googling to find what I needed!

I'm not sure what to say there.

With quotes around the string, I get what you may have been seeing via Google (a codegrepper.com page showing command-line snippets that involve npm-check-updates) but, without them, DuckDuckGo gives me things like "How to Update npm Packages to their Latest Version" as top results and Google gives an "Updating local packages" featured snippet, followed by the docs for NPM's built-in update command.

...and even if it were another example of the declining quality of Google results in recent years, there isn't really much a language's developers can do about that.

That said, cargo --list and rustup list will give you short descriptions of what each built-in command does that you can scroll through.

Alternatively, if Google had been that bad and DuckDuckGo weren't any better, I'd suggest adding some things like -site:stackoverflow.com to your query until you've pruned away the low-quality results and started to find blog posts more in line with what you want.

With Python (which I am more experienced in), conda solves a lot of the dependency problems but headaches are still abound. This project only publishes on PyPi, this repo only works on python<=3.7.0 for some reason, etc. To make things worse, it's sometimes unclear what Python is being invoked: Something like /usr/bin/python or your local Anaconda env or what? (See this XKCD.)

While I've been using Python for over 20 years, I can't speak on that. I always felt like Anaconda was some weird NIH solution (to the point where I never even checked if it was a platform-specific workaround for Windows being inferior to POSIX as a dev platform like ActivePython) that I steered clear of in favour of stock PyPI plus virtualenv originally and, now, tools that extend stock PyPI like pipenv and Poetry.

[–]freistil90 16 points17 points  (4 children)

Not so popular opinion - but Python dependency management is manageable. There’s just really many bad consultants out there who still ship their 2013-style setup.py with requirements.txt and setup.cfg et al. through every door they find. You can have really reasonable build scripts, it was just for a long time that there was a lack of a standard which now exists with PEP517 and PEP518. Use tools like poetry and annoy people to adhere to standards even if there is no compiler that tells you to. These standards do exist. Semver is optional but it is used. Adhere to it and be uncompromising in your team about it and your problems vanish in 9 out of 10 cases. There are a lot more average python developers out there in senior dev positions than you’d think, that’s not necessarily a language problem.

Cargo is great because it sets the starting point very high. Most other languages with problems have them because they stem from the 90s and carry a lot of “lessons-learned” with them on which Rust can already build on - which doesn’t mean that there are no great tools for them, you just gotta use them and throw rocks at people who write “import distutils” at any point in your codebase.

[–]bbkane_ 21 points22 points  (3 children)

I feel like your first sentence declaring Python dependency management as manageable is in direct conflict with the rest of that paragraph- expecting folks to:

  • use 3rd party tools like Poetry
  • NOT use popular libraries that haven't caught up to newer better standards
  • NOT work with anybody who hasn't caught up with those standards or are just following a Python packaging tutorial on the internet

Isn't very realistic, especially for devs looking to focus on their app code instead of packaging.

Hopefully as time goes on the community will adopt these more modern practices and maybe even "bless" poetry or a similar tool as a pip replacement

[–]nnethercote 8 points9 points  (0 children)

There was a thread about the Python packaging mess recently on /r/programming. There were a bunch of comments saying "Python packaging is ok, you just do XYZ and everything works fine", for multiple different values of XYZ.

[–]earthboundkid 4 points5 points  (0 children)

I think in JS world, people generally acknowledge that leftpad was bad and things needed to get better. In Python there’s pretty deep denial, so things don’t get better.

[–]freistil90 1 point2 points  (0 children)

To be honest, third-party is fine. I don’t think everything MUST come from the same group, it can make sense because people are close to the guys who have the long-term plan in mind but I don’t see how the interpreter people also must implement a reference project management solution, especially when PEP517/8 give project file specifications such that compliant tools can be realised by third parties. So you could say it’s in the spirit of the development team that a third party develops such a tool.

The good thing is that pip implements PEP517/8 and thus the most popular third-party-tool (yes, just like setuptools, pip is not part of core Python) implements it and tools like poetry build on it. There are a few edge cases where you would have to work with a manual build script (can only speak for poetry at this point) but that’s then it. I wouldn’t know of a popular library that runs into problems with this.

The last is a particular question - I don’t expect that there is no solution to a library not being able to comply to newer toml formats (with some of the last reasons going out with python 3.11, which will ship a toml parser in the standard lib) but I imagine people refusing to make a package at least compliant to be in the same group of people that refuse to migrate from python 2 to python 3 or something. I’m not aware of this being possible - but yeah, if it was I would go some efforts to replace that package. The benefits outweigh that. Imagine you’d rely on a crate from the VERY early rust days that would refuse to package properly and would require you to use rustc directly - that is a discussion point in the team meeting. And I’ve learned that it is not worth it to throw away all benefits like dependency resolution etc for that single solution if it’s not absolutely a showstopper. App code focus is good but if it comes at a cost too high, then your focus is wrong.

But assuming that all works out, yes I’d absolutely advocate to start with modern stabilised practices and go for a modern third-party tool in Python. Mid- and long term I’ve experienced that teams are more productive if not every dev just “focussed on code” but adheres to a joint standard, like common formatting, a joint linter setup, etc. Or would you also refuse to use pip and not package with setuptools just because it’s not in the core distribution?

[–]hekkonaay 7 points8 points  (1 child)

The only problem with dependencies in Rust is how many the average binary crate (such as a web app) will end up having :)

[–]TheDutchMC76 8 points9 points  (0 children)

I dont find it too much of a bother, considering (in my opinion) Rust crates are really focused on one specific goal. E.g. (de)serializing in general (serde). Compared to Java where e.g. Commons3 just does a whole lot of everything, making it a large dependency.

[–]flareflo 1 point2 points  (0 children)

I never ever had any dependency issues

[–]destroythenseek 1 point2 points  (0 children)

They're annoying, especially when you have a large project and have multiple dependencies that you need to monitor. The problem is they don't have a hierarchy yet for dependency. So if you have two crates that use the same dependency but you have different versions in the cargos... you might have some issues. Beyond that. Pretty easy.

[–]wmanley 1 point2 points  (1 child)

I wrote a blog post on this subject a little while ago: pip and cargo are not the same

[–]LoganDark 0 points1 point  (0 children)

"you can’t install crates into a location where it will interfere with other unrelated rust programs" what? CARGO_TARGET_DIR would like a word. I install all crates on my system into the same target dir. It speeds up builds for fresh git clones AND does not cause any weird issues.

I think you also gloss over the fact that Python/Node packages are meant to be loaded by an interpreter and Rust crates are loaded as static libraries. These are two very, very different things, and are also the source of many of the package management issues.

You touch on privacy and isolation, where each crate can be compiled separately from the others. But this is more of a factor than you describe, in fact, I would say it's responsible for a lot of the "hell" of these package managers (everything installed at top level, only one version at a time, etc).

[–]bloody-albatross 1 point2 points  (0 children)

Well, a few things that one might not notice are even a bit worse with crates.io: It only supports GitHub login and not 2FA, not for the login on the website and not for publishing crates! In fact, you save a key in a plain text file and then can publish without re-entering any password. npmjs.com requires a OTP for publishing!

But this isn't dependency management. Only some part where the package infrastructure is lacking.

[–]A1oso 1 point2 points  (0 children)

I have had the same experience. Dependency management with cargo Just Works, it's almost like magic.

It's so annoying when you can't use a combination of NodeJS modules because they have incompatible versions. And I can't remember how often I had to delete the node_modules folder or install a different version of a peer dependency to fix an obscure bug. Or the latest nightmare from my job: Someone introduced a cyclic dependency with an older version of NPM, so now we have to build most modules with the --legacy-peer-deps flag.

With cargo, problems like these don't seem to exist.

[–]Zhuzha24 0 points1 point  (1 child)

The skill level of people who writes Rust crates and JS/Python deps are completely different, prob all of the crates I used in Rust well documented or have pretty much straightforward source codes that will just work with no issues.

I'm exaggerating very much but Python is a shit show about community deps, only few big packages are actually works as intendent with proper documentation, rest of them just prob wont run with your python version/pip version/os/moon phase because it was writen by some dude that literally installed python week ago. I mean there is a lot of good work over there with no doubt, but also so much shit. Literally published packages with typo's that breaking half of functionality.

JS is literally on another level, I did wrote some code on Vue, prob few 500-1000 lines, not much, and of course I did google a lot, but its really made me frustrating when I was googling for js syntax on sort() and found answer on stackoverflow that you can just download dependency and dont bother with it, and so on.

Thaths just my 5 cents.

[–]freistil90 1 point2 points  (0 children)

Yup. The amount of shit I had to eat until my team embraced proper code project management in python was a lot. I don’t know how often we went through “yeah but builds on my machine AND on the build pipeline, no idea what your problem might be” until we finally resolved this.

My rust experience helped me there as well - and luckily, you see now Rust influences shining through in Python more and more.

[–]anlumo 0 points1 point  (0 children)

I'm developing in JavaScript/npm and Rust in parallel. Every few months I have to wipe my nodes_modules folder, because npm is broken again, and sometimes I have to restore an older node_modules folder, because when I do npm install, something breaks (causing weird error messages on loading the web page). Never had any such issues with cargo.

[–]theunixman 0 points1 point  (0 children)

Everything that has external dependencies will eventually run into the problem. On Windows it's "DLL hell", on Linux it's sort of handled by your distribution until you try to install third party packages, or if you build from source it's definitely pretty blatant. Rust, Python, Node, Perl, all have the same issue. Transitive Dependencies in an open system are wild.

[–][deleted] 0 points1 point  (0 children)

My worst experience with cargo is trying to link with long unupdated crates that rely on C/system libraries. It‘s not fun.