Hacker News new | ask | show | jobs
by yorwba 664 days ago
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.

1 comments

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.

The idiomatic Rust way to avoid a scenario where we might have different versions of the unique IDs is to reify unique IDs as an actual type (say named UniqueID) rather than just having a tacit understanding that these particular integers are supposed to be unique.

This lets the type's owner decide what affordances to give it (probably adding two UniqueIDs together is nonsense, but comparing them for equality definitely makes sense - should they be Ordered... chronologically? Or not?)

But importantly here now that it's a type, Rust knows counter v 1.2.3 UniqueID and counter v 4.5.6 UniqueID are different types, even though they have the same spelling. So this code now won't compile unless everybody is consistent e.g. the Frogs and Wizards all use v 1.2.3 but the unrelated Space Combat module works with v 4.5.6 UniqueID. Code that looks like it could work, where we try to use the unique ID of a Wizard as the identify of a sub-space laser blaster won't compile.