Both :) But what bothers me most, is the slow incremental debug builds. But that's a general Rust problem, although seems extra bad with Tauri.
My project has ~1700 lines of Rust, ends up pulling in ~700 dependencies and changing one line in main.rs takes about ~20 seconds for the debug compile to finish (on a 5950X CPU). Fresh build takes about 1m30s but that only happens on CI, so not really a problem day-to-day for me. Full CI build on Codeberg/Woodpecker takes around 5 minutes from push to release created, but that's including other things too.
> [...] My project has ~1700 lines of Rust, ends up pulling in ~700 dependencies [...]
I have no experience with Rust. Having 0.4 dependencies per line (so every 3 lines you are dependent on a 3rd-party) sounds very extreme. Can you elaborate a bit what those dependencies are (like many std libs?)?
Thanks a lot for taking a look at the dependencies! I'm gonna be honest and say that I haven't spent much time "optimizing" anything, I haven't even arrived at a "first" version yet with Ditzes. But with that said, you have some good points :)
> How come you pull in both isahc and reqwest? On the surface, don't they fill the same role? Similar for rustls and openssl.
Yes indeed. I started with using reqwest, but it's no longer used and only isahc is used, so reqwest can be removed from the list.
Same with openssl/rustls. Started with rustls, but not longer used, so can also be removed.
Simply forgot to remove them at one point I guess.
> If you upgrade your readability dependency, you will only pull in reqwest once instead of twice. Might be useful to run `cargo tree -d`.
Ah, that's good to know. I'll do that once I put in some other changes, thanks!
> I'm not familiar with savefile? What does that get you over serde_json which you also pull in?
Rust savefile persists structs as binary data on disk, and also have versioning. Started out persisting everything as JSON, but loading thousands of posts (or even hundreds) from disk and deserializing them from JSON turned out to be quite slow. So using savefile mainly for performance, but I like the versioning/migration aspect of it as well, although I haven't used it yet with Ditzes.
serde_json is mainly used to parse responses from the HN API and to output JSON from the HTTP API, if I recall correctly.
That will be including transitive dependencies. Consider how many dependencies making a HTTP request will pull in. Ones for network IO, ones for parsing HTTP request. Possibly separate ones for HTTP1, HTTP2, HTTP3. Ones for type definitions of HTTP requests. Rust convention is to publish these bits as separate crates even if they are published by the same maintainer(s) so as to improve compile times (separate crates can be compiled in parallel) and modularity (if you're writing a competing library then you can share the internals of the existing implementation when possible).
This isn't like C++ where a single dependency is a huge project. Some crates are literally just trait (interface) definitions.
Indeed. Here I took a random sample from https://github.com/topics/rust and listed the amount of dependencies from running `cargo tree` (full command: `cargo tree | awk 'NF' | wc -l`)
- denoland/deno (nodejs-like runtime) - 1550
- alacritty/alacritty (Terminal) - 303
- sharkdp/bat (`cat` clone) - 249
- meilisearch/meilisearch (search engine) - 1128
- starship/starship (command line prompt) - 427
- swc-project/swc (JS/TS compiler) - 1869
- AppFlowy-IO/AppFlowy (Notion clone) - 1146
- yewstack/yew (Rust/WASM web framework) - 942
- rustdesk/rustdesk (remote desktop) - 648
- nushell/nushell (shell) - 858
- ogham/exa (`ls` alternative) - 61
So yeah, seems even things as basic as a `cat` replacement ends up with 249 dependencies. Lowest amount of dependencies is a `ls` alternative, which ends up with 62 dependencies.
cargo tree | wc -l tends to over-count dependencies a lot because there are many very common crates.
Use grep package -F '[['package Cargo.lock | wc -l instead. This is doubly true if you pull in multiple crates using the same framework behind it, e.g. in async projects. E.g. for exa this is 45 instead of 61. For deno this turns into a 3x difference (515 vs. 1571).
Other reasons, besides what has been mentioned (vendoring being very uncommon, crates existing for common type and trait definitions, crates being generally scoped more narrowly resulting in more smaller crates), a lot of crates have additional crates for macros. Bindings often end up being a whole bunch of crates for this reason (i.e. one xyz-sys crate for the FFI bindings, plus one or more crates for a rusty wrapper and perhaps one or more crates for convenience crates). Case in point from deno's dependency tree:
Deps are a transitive tree. The number of deps for enduser code for a framework like this will always be high. That is kinda the point. A small amount of end user code uses the work of thousands.
I wonder if there are any plans to buttress this issue with Rust. Last time I checked the issue was that there is/was no ABI stability which meant you couldn't have precompiled libraries to build against. The only way around it at the time was to expose C compatible APIs, but that means of course giving up much of Rust's strengths.
I recently developed a similarly sized APP with tauri + rust (3kLOC+~250 deps) and found that using mold[0] as a linker helped quite a bit for incremental builds. There were also a few other bits, like decoupling the build step in tauri from the frontend build, that helped.
Yeah, separating things is what I ended up doing as well. Ditzes is currently four parts: API, CLI, frontend (CLJS though) and desktop application. Most of the time when developing, only the API/CLI/frontend changes, while I just do desktop builds to check it from time to time.
Incremental builds are unlikely to benefit from multiple cores. I have a 5900x Desktop and recently bought a Laptop with 12700H. For some incremental builds, I'm getting the same performance. The Desktop Ryzen shines with full multiple builds (where the laptop gets hot and has to sabotage itself); or with running multiple programs without noticeable performance impact.
I haven't tried mold. I already face lots of difficulties with the current linker since I need to deploy to multiple architectures, so I can't imagine how that would be with Mold (but then maybe that's why I should try it!)
What do people compare with when they complain about 20 second build times? That doesn't seem like anything for a thing that one doesn't need to do very often. You can still do cargo check without taking so much time, right?
`cargo check` is what my editor runs automatically to find type errors, that runs automatically on every save. It doesn't allow me to actually "check" that the application does what I want it to. I run `cargo run/build` when I need to "humanly" test the application, and waiting 20 seconds for it is pretty bad.
But then I come from a dynamic application development background (mostly Clojure), so maybe I do have a pretty unfair view on how fast I should be able to make a change and see the effects (sub second is the usual wait time in Clojure land).
Out of curiosity: fresh builds or also incremental builds? If the latter, how much of it is linking?