Hacker News new | ask | show | jobs
by alkonaut 668 days ago
How does this work? Assume that the log crate in its internal state has a lock it uses for synchronizing writing to some log endpoint. If I have two versions of log in my process then they must have two copies of their internal state. So they both point to the same log endpoint, but they have one mutex each? That means it "works" at compile time but fails at runtime? That's the worst kind of "works!"

Or if I depend transitively on two versions of a library (e.g. a matrix math lib) through A and B and try to read a value from A and send it into B. Then presumably due to type namespacing that will fail at compile time?

So the options when using incompatible dependencies are a) it compiles, but fails at runtime, b) it doesn't compile, or c) it compiles and works at runtime?

1 comments

If the log endpoint is external to your process and two different copies of the logging crate in the same process writing to it cause problems, two identical copies of the logging crate in different processes will likely also cause problems. The solution here is global synchronzation, not just within one process.

If the log endpoint is internal to your process, how did you end up with two independent mutexes guarding (or not guarding) access to the same resource? It should be wrapped in a shared mutex as soon as you create it, and before passing it to the different versions of the logging crate. And unless you use unsafe, Rust's ownership model forces you to do that, because it forbids having two overlapping mutable references at the same time.

Perhaps a log wasn't the best example due to how the resource (a log sink) is often external. Take some simpler example: a counter (such as sequential ID generator).

It's an in memory counter doing an atomic increment that returns the next ID. Two of my projects in depend on it when they create new items. Both want to generate process wide unique IDs. But if they depend on two versions of the crate then there would be two memory locations, and thus two sequences of IDs generated, so two of the frogs in my game will risk having the same ID?

There is no sharing problem here, the problem is the opposite: that there are two memory locations instead of one?

For the counter example, with what I assume was an edit change about frogs, each of the two distinct counter versions produces, as it promised, unique IDs.

If the frog numbering code is using either counter, it gets unique IDs. The problem only arises if somehow it's using both of them. But why would we expect the "unique" IDs from two different pieces of software be guaranteed never to collide? Imagine instead of counter 1.2.3 and counter 4.5.6 we've got bob_counter and cathy_counter, are you still astonished that they might give out the same IDs? Neither mentions the other, perhaps they're the same code, copy-pasted by egomaniacs.

> are you still astonished that they might give out the same IDs?

No I think two sequences is exactly what's asked for. What I'm wondering is whether this is a warning when cargo fails to restore the crates and must use incompatible versions. I'd only be surprised if the behavior was a clean compile + a runtime crash. Because that's usually not the design chosen in Rust.

> If the frog numbering code is using either counter, it gets unique IDs

I guess this is the issue: what I'm imagining is that the counter crate has a static mutable state. That's not a problem with atomics, but it's an antipattern (And especially so in Rust). The solution of course is to NOT use static mutable state at all. Whoever wants to create either a Frog or a Wizard must pass in a universe, from which it can grab the next ID, so it's a counter instance rather than get_next_static_id().

My question isn't "do we really get two memory locations" (of course we do) my question is: how afraid should one be about this, i.e. what is the behavior of the compiler/cargo or other linters when it comes to warnings etc? Are there any non-contrived scenarios where bumping a version of a dependency causes a problem at runtime (only)?

> What I'm wondering is whether this is a warning when cargo fails to restore the crates

What does "fails to restore the crates" mean here?

I've seen this verb "restore" used in some .NET software, but I can't see how it relates to Rust.

Sorry yeah I mean "resolve the dependency versions" from the listed version requirements, and if needed downloading them. "Restore" is used by e.g. NuGet (.NET) as you suggest and others (npm for js, etc).

The cargo dependency resolver runs and the results can be viewed by cargo tree. I saw in the docs now that cargo tree -d can be used to help find incompatible packages.