Out of curiosity: fresh builds or also incremental builds? If the latter, how much of it is linking?
Out of curiosity: fresh builds or also incremental builds? If the latter, how much of it is linking?
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.
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?)?
This isn't like C++ where a single dependency is a huge project. Some crates are literally just trait (interface) definitions.
My project have ~30 dependencies defined for various features in the client. The rest are transitive.
Here is the output of cargo tree for your leisure: https://pastebin.com/aep1bX4v
A quick scan of cargo tree seems to indicate most of them comes from tauri and actix projects.
Edit: oh, and also tantivy (a search engine) ends up pulling in ~300 dependencies which is a pretty big chunk of the total
- An electron-like framework
- A web framework
- Multiple HTTP clients
- An HTML parser
- An image editor
- A search engine
Some potential dependency savings I noticed:
How come you pull in both isahc and reqwest? On the surface, don't they fill the same role? Similar for rustls and openssl.
If you upgrade your readability dependency, you will only pull in reqwest once instead of twice. Might be useful to run `cargo tree -d`.
I'm not familiar with savefile? What does that get you over serde_json which you also pull in?
> 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.
- 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.
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:
├── futures v0.3.21
│ ├── futures-channel v0.3.21
│ │ ├── futures-core v0.3.21
│ │ └── futures-sink v0.3.21
│ ├── futures-core v0.3.21
│ ├── futures-executor v0.3.21
│ │ ├── futures-core v0.3.21
│ │ ├── futures-task v0.3.21
│ │ └── futures-util v0.3.21
│ │ ├── futures-channel v0.3.21 (*)
│ │ ├── futures-core v0.3.21
│ │ ├── futures-io v0.3.21
│ │ ├── futures-macro v0.3.21 (proc-macro)
│ │ │ ├── proc-macro2 v1.0.38 (*)
│ │ │ ├── quote v1.0.18 (*)
│ │ │ └── syn v1.0.93 (*)
│ │ ├── futures-sink v0.3.21
│ │ ├── futures-task v0.3.21
│ │ ├── memchr v2.5.0
│ │ ├── pin-project-lite v0.2.9
│ │ ├── pin-utils v0.1.0
│ │ └── slab v0.4.6
│ ├── futures-io v0.3.21
│ ├── futures-sink v0.3.21
│ ├── futures-task v0.3.21
│ └── futures-util v0.3.21 (*)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).
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.