Same dependency version as sub-dependency. Is it needed?

Some context:

I have a Tauri GUI application, which also spawns some warp http(s)/s servers in the background. Recently I have had the idea off speeding up initialization using a sqlite database instead of a multitude of small text files. I have found that a sqlite connection (as abstracted by rusqlite) cannot be shared safely between threads; thus I can either create a connection on every new thread (which seems wasteful since it seems tauri spawns a thread per command, and it may need some synchronization to enforce sqlite's requirements for multi-threaded use) or create a dedicated thread that 'queues' actions on the db.

Anyways, for all of that, I need to access the Tokio runtime that Tauri uses, which can be done through Tauri's API.

The 'problem', I recently found/rediscovered is that I am using libraries that depend on Tokio, and some functionality of Tokio itself that Tauri does not expose, and I remember reading cargo can package several versions of the same library.

Yet, surprisingly, my abominable ball of glue code compiles and works; Cargo.lock registers only one version of tokio. But am I relying on a happy coincidence or is this expected behavior?

In short: How can I instruct cargo to use the same version of a dependency that some other dependency requires? / Is it needed? / When does several versions of dependencies in the same binary happen? / Can I only expect compile-time errors if I get incompatible versions?

Greetings, thanks for reading.

It would surprise me if the answers can not be found in here: Dependency Resolution - The Cargo Book.

Don't worry about "wasteful" just yet. If you are spawning multiple HTTP servers, then opening an SQLite connection per server is not going to be the bottleneck.

The bigger problem with connection-per-thread is that they are in the same process, and SQLite doesn't provide isolation between connections within the same process. This means that your transactions will go haywire. So please just don't open multiple connections per process, regardless of threads or efficiency or anything like that – it's simply not a correct thing to do (if you are ever planning to write to the database, that is).

This is the generally recommended solution.

It's not needed. If Cargo concludes that multiple dependencies require conflicting (i.e., not semver-compatible) versions of the same downstream dependency, then it will compile all required incompatible versions, and fuse compatible versions together. You generally don't need to worry about it, except that you need to know that such "duplicates" of the conflicting crates will be considered completely different crates, so crate_v1::Foo will be considered a different type from crate_v2::Foo.

1 Like

There is the tokio_rusqlite crate which does this to bridge rusqlite with async, it creates a background thread that has a synchronous rusqlite::Connection, and it communicates with it using channels.

2 Likes

Thanks for the replies!
tokio_rusqlite may not be the best option right now, because the sqlite database is already abstracted away (in a questionable design decision). In the end, I might end reinventing most of it, however, oh well.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.