In this section

It takes about fifteen minutes. We assume QuickRust is installed and that you have done Your first build with QuickRust, so cargo build and QuickRust’s summary lines are familiar.

Build a program with dependencies

Create a project and give it two dependencies:

cargo new inventory
cd inventory
cargo add serde --features derive
cargo add serde_json

Each cargo add ends in an OK line, with the features of the dependency listed above it.

Replace src/main.rs with:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct Item {
    name: String,
    count: u32,
}

fn main() {
    let items = vec![Item { name: "apple".into(), count: 3 }, Item { name: "pear".into(), count: 5 }];
    let json = serde_json::to_string_pretty(&items).unwrap();
    println!("{json}");
    let back: Vec<Item> = serde_json::from_str(&json).unwrap();
    println!("{} items", back.len());
}

Build it:

cargo build

This build takes a few seconds. The status line counts the crates as they finish; at the end you will see something like:

     Downloaded 10 crates, 924.3 KiB [0.12s]
OK   Built: 17 compiled, 1 restored from QuickRust's cache [2.00s]

If you did the first tutorial, one crate (itoa, which serde_json uses) was restored from QuickRust’s cache instead of compiled. Your numbers and versions may differ a little; the steps below work the same.

See the critical path

QuickRust recorded every compiler run of that build. Ask it about the timing:

qrust analyze timing
Timing
  critical path: 1.78s  [observed]
  ON CRITICAL PATH      KIND              DURATION  STATE     EDGE
  [email protected]   build-script      141ms     compiled  observed
  [email protected]   build-script-run  115ms     ran       inferred
  [email protected]   lib               139ms     compiled  inferred
  [email protected]          lib               91ms      compiled  observed
  [email protected]             lib               435ms     compiled  observed
  [email protected]  proc-macro        706ms     compiled  observed
  [email protected]         lib               125ms     compiled  observed
  [email protected]       bin               106ms     compiled  observed
  slowest units (15 of 24):
  UNIT                  KIND              MODE   DURATION  CRITICAL  ROLE    CACHE
  [email protected]  proc-macro        build  706ms     yes       host    stored
  [email protected]             lib               build  435ms     yes       host    stored
  ...
  [email protected]    lib               build  541ms     no        target  stored
  ...
  peak concurrent compilers: 9   cpu time: 9.13s   peak rss: 372.6 MiB

The first table is the critical path: the chain of steps, each waiting for the one before, that decided how long the build took. Read it from the top. proc-macro2’s build script is compiled and run, then proc-macro2 itself, then quote, then syn, then the serde_derive macro, then serde, and only then our own program. Together they take 1.78 seconds of the 2.00.

Notice serde_core in the second table: it took 541 ms, longer than most steps on the path, but it is marked no under CRITICAL. It was compiled while the path was busy elsewhere, so making it faster would not have finished the build any sooner.

Notice also the last line: the compilers used 9.13 seconds of CPU time in a build that took 2.00 seconds, because up to 9 of them ran at once.

Look at the macro and the build scripts

serde_derive is the longest step on the path. Ask about procedural macros:

qrust analyze proc-macros
Procedural macros
  MACRO CRATE           STATE     OWN COMPILE  DIRECT USERS  DOWNSTREAM  DOWNSTREAM TIME  CLASS
  [email protected]  compiled  706ms        1             2           230ms            critical-path-candidate
  Proc-macro execution timing
    Unavailable: a stable toolchain does not expose time spent expanding macros in dependent crates; only the macro crate's own compilation is measured

The class critical-path-candidate says what we saw above: this macro sits on the critical path. The last line is QuickRust being honest about what it cannot measure; it says “unavailable” rather than showing a zero.

Now the build scripts:

qrust analyze scripts
Build scripts
  PACKAGE              THIS RUN  RUN TIME  COMPILE  RERUN-IF       OUT_DIR  RUNS SEEN  STATE
  [email protected]  ran       115ms     141ms    3 file, 1 env  0 B      1          observed
  [email protected]         ran       13ms      137ms    1 file, 0 env  0 B      1          observed
  [email protected]        ran       12ms      151ms    1 file, 0 env  4.0 KiB  1          observed
  [email protected]   ran       15ms      166ms    1 file, 0 env  4.0 KiB  1          observed
  [email protected]   ran       2ms       69ms     1 file, 0 env  0 B      1          observed
  [email protected]          ran       14ms      132ms    1 file, 0 env  0 B      1          observed
  No broad invalidation detected.

Six crates have build scripts. Each one is compiled and then run before its crate can be compiled; the RERUN-IF column says what each declared it depends on, which decides when it runs again.

Change one line and rebuild

Open src/main.rs and change count: 3 to count: 4. Build again:

cargo build
OK   Built: 1 compiled, 17 fresh [154ms]

Only our program was compiled. Ask why:

qrust analyze rebuilds
Rebuild reasons
  REASON  UNITS  TIME
  source  1      90ms
  previously known units rebuilt: 1 of 18 in the index (ratio 0.056)
  UNIT             KIND  REASON  COST  CONFIDENCE  DETAIL
  [email protected]  bin   source  90ms  high        changed: src/main.rs (modified)

QuickRust names the reason (a source file changed), the file, and how sure it is. When a build rebuilds more than you expected, this is where to look.

Compare the two builds

Every build is a recorded run. List them:

qrust runs
RUN                         STARTED                   FAMILY       STATE           WALL  OBSERVED  COMMAND
01M4SFW9PX1526541E7WM1ZJ9F  2026-10-13T02:01:19.327Z  build        completed      154ms  rustc     cargo build
01M4SFW1V3NZTKFD0YHGA3GXES  2026-10-13T02:01:11.267Z  build        completed      2.00s  rustc     cargo build
01M4SFW1JF0TMXB0QNFFZFA7PF  2026-10-13T02:01:10.991Z  passthrough  completed      246ms  session   cargo add serde_json
01M4SFVZWTN755HZZ5TKTQ5W32  2026-10-13T02:01:09.274Z  passthrough  completed      1.69s  session   cargo add serde --features derive

We do not need the long identifiers: latest is the last run and latest~1 the one before it. Compare the full build with the rebuild:

qrust compare latest~1 latest
Comparison: run 01M4SFW1V3NZTKFD0YHGA3GXES  ->  run 01M4SFW9PX1526541E7WM1ZJ9F
  comparability: comparable
  METRIC                    BASELINE     CANDIDATE    DELTA
  wall_time_ns              2.00s        154ms        -92.3%
  critical_path_ns          1.78s        90ms         -95.0%
  sum_unit_time_ns          3.61s        90ms         -97.5%
  cpu_time_ns               9.13s        134ms        -98.5%
  peak_rss_bytes            372.6 MiB    126.3 MiB    -66.1%
  units_compiled            17           1            -94.1%
  units_restored            1            0            -100.0%
  ...

Notice the second line: QuickRust first checks that the two runs can be compared at all (same toolchain, same machine, same kind of command) before it shows any difference.

Write a report

Finally, write the full build’s analysis as a web page:

qrust report --run latest~1 --format html -o report.html
OK   Wrote report.html

Open report.html in your browser. It holds everything we looked at, every section on one page, and needs no network.

What we have done

We built a program with real dependencies and found its critical path, saw that a procedural macro and the crates it needs decided most of the build’s time, looked at the build scripts, found out exactly why a rebuild compiled one crate, compared two builds, and wrote a report.

Where to go next