Tidy Tank's job sounds simple: look at every file on your disk, add up the sizes correctly, and do it fast enough that you don't wait. On a developer's Mac that can mean two million files or more. Here is why the scan engine is written in Rust, what it does, and what it measured. All numbers come from the developer's own test notes, and we say where they come from.
The short answer
- One engine for Mac and Windows. The same Rust code builds for Apple silicon and Intel Macs and for Windows on x64 and ARM64.
- Close to the operating system. Rust can call macOS's fastest folder-reading call directly, without a runtime in between.
- Safe threads. The scan uses several threads at once. Rust's compiler refuses code where two threads could corrupt the same data.
- Measured speed. A repeat dry run on the developer's Mac dropped from 19–20 seconds on the old path to about 3.5–4.7 seconds.
The problem: millions of small files
A disk cleaner spends almost all its time asking the file system questions: what is in this folder, how big is this file, is this a link, when did it change. A typical developer home folder has hundreds of thousands of folders. The test Mac's home folder had about 2.13 million files in 404,000 folders, about 201 GiB.
Asking about each file one at a time, as a simple script does, costs one system call per file. That adds up to tens of seconds. The earlier Tidy Tank scan, written in Python, needed 19–20 seconds for a full dry run on that Mac, and version 0.4.8 needed 34 seconds.
Speed is not the only issue. The answer also has to be right: count hard links once, skip cloud placeholders, never follow a symbolic link out of the folder, and give the same total on every run.
One engine instead of two
The alternative to Rust was to write the scan twice: once in Swift for the Mac and once in C# for Windows. That means two sets of bugs, two sets of tests and two ways of counting the same thing.
With Rust there is one engine, tidytank-engine. The Mac app and the planned Windows app both start it as a separate helper process and talk to it in simple lines of JSON. Only the part that reads folders is different per system:
- on macOS, a fast lister built on
getattrlistbulk, and - a generic lister (
readdirpluslstat) that works on other systems and is the starting point for Windows.
Everything above that, such as size rules, hard-link handling, the warm cache and finding developer folders, is shared.
The cleaning rules are shared too. The engine applies the same deny rules as the rest of the app. They are not typed in twice: the app generates the engine's rule file from its own list, and a test checks that both agree.
How the engine reads a disk fast
Whole folders in one call
macOS has a system call, getattrlistbulk, that returns the names, types and sizes of many folder entries at once. The engine reads each folder in 256 KB chunks with it, instead of opening and checking every file separately. Rust calls it directly through libc, with no wrapper or garbage collector in the way.
The right number of threads
Folders are spread over a pool of worker threads using rayon, a Rust library for work stealing: a thread that runs out of folders takes some from a busy one. The engine uses as many threads as the Mac has performance cores, up to 8. The code notes why: more threads than that made it slower on macOS, because they fight over the kernel's file name cache, and efficiency cores add nothing to this job.
In Rust, sharing data between threads unsafely is a compile error, not a crash you find later. For a program that walks millions of folders on many threads, that matters.
Remember the last scan
Most of your disk doesn't change between two scans. So the engine saves the folder tree after each scan in a compact binary file. Next time it asks macOS's change journal (FSEvents) which folders changed since then, and only re-reads those. Unchanged folders cost no file system calls at all.
Huge flat folders, such as browser cache folders with about 100,000 files, keep a per-file index, so a few changed files are patched in place instead of re-listing the whole folder. If the journal can't be trusted, for example after a disk swap, the engine falls back to checking each folder's modification time.
Results while it walks
During a first scan the engine sends progress four times a second and sends the first big finds as soon as the most likely folders are done. You see useful rows after about 6 seconds even when the full walk takes about 20.
Correct, not only fast
- Real disk usage. The engine records allocated bytes as well as file length, so sparse files and empty cloud placeholders don't inflate the total.
- Hard links once. After the walk it recounts folder totals in a fixed order and counts each hard-linked file once, in the first folder by name. Two runs give exactly the same folder sizes, and a test checks this over repeated runs on 8 threads.
- No surprise downloads. It tells macOS not to download online-only iCloud and File Provider files while it reads.
- No following links. Folders are opened with
O_NOFOLLOW, so a symbolic link can't lead the scan somewhere else. - Same answer as
du. On one large test folder the engine took 8.1 seconds wheredutook 41, with the same sizes. - Cache that tells the truth. A warm scan and a fresh full scan taken 20 seconds later differed by 1.9 MB out of 201 GiB, and 4 files: files that really changed in between.
- Finds more. Because it detects developer folders during the walk instead of asking Spotlight, it found 38 more build folders (about 2 GB) that Spotlight had missed.
The numbers
All measurements: the developer's Mac with an M2 Pro on macOS 27.2, home folder of about 201 GiB with 2.12–2.13 million files, notes dated 3 November 2026. "Warm" means a previous scan is cached. These are one machine's results, not a benchmark suite. Your Mac will differ.
| What | Time |
|---|---|
| Full dry run, version 0.4.8 (old baseline) | 34 s |
| Full dry run, Python path | 19–20 s |
| Full dry run, Rust engine, warm, first version | 16–18 s |
| Full dry run, Rust engine, warm, now | 3.4–4.7 s (most runs 3.5–3.7 s) |
| Full dry run, Rust engine, cold | about 24 s (first results about 6 s) |
| Engine walk alone, warm (FSEvents) | 0.6–0.85 s |
| Engine walk alone, cold | about 18–20 s, peak memory about 240 MB |
Engine vs du on one large folder | 8.1 s vs 41 s, same sizes |
A "full dry run" is the whole app: the disk walk plus all the detectors that recognise caches, simulators, apps and so on, without deleting anything.
Safety by design
- Little
unsafecode. Rust marks code that the compiler can't check asunsafe. In the engine it appears only where it talks to macOS: the system calls and reading their result buffer, in two source files. - Few dependencies. Five:
rayonfor threads,serdeandserde_jsonfor JSON,bincodefor the cache file, andlibcfor system calls. Fewer dependencies means less code to trust. - A separate process. The engine runs as its own process. If it crashes, times out or sends something unexpected, the app notices and falls back to its slower built-in scan. A bug in the engine can't take the app down.
- It only reads. The engine never deletes anything. The only file it writes is its own cache, readable only by your account. Cleaning is done by the app, which re-checks every item first.
Honest limits
- The first scan is limited by the disk. A cold walk of two million files still takes about 20 seconds on a fast SSD. The engine streams results so you can start reading earlier.
- The rest of the warm time is not the engine. In a 3.5-second warm run the engine is ready in about 0.6 seconds. Most of the rest is the app's per-file safety checks and detectors, written in Python. Moving those checks into the engine is the next step toward under 3 seconds.
- Still being switched on. As of the 3 November notes, the Rust engine was opt-in and the default was still the Python path. Making Rust the default was the next decision.
- Windows isn't built yet. The engine was designed for Windows from the start, but the Windows version has not been built or tested.
Want to know what happens with the numbers the engine produces? The FAQ explains Listed vs Gets back and how Tidy Tank decides what is safe. For habits that keep your disk from filling up, see How to free up disk space on a Mac or PC, safely.