The working file. Kept for the wrong turns.
No Rust here at the start, and no Visual Studio either. rustup is the official installer
and needs no elevation. It writes to %USERPROFILE%\.rustup and %USERPROFILE%\.cargo
and adds .cargo\bin to the user PATH. Wrapped as
tools/install-rust.ps1 in the same shape as the Go and
Elixir installers: download, check SHA-256, install, print the version.
It failed twice before it worked, both times on Windows PowerShell 5.1, not on rustup.
checksum mismatch
expected 54
actual 6f4bef66261261fcb43131be8720bab817d403a09edec7455c371974b90bdb7e
54 is the ASCII code for 6, the first character of the real hash. The .sha256 file is
served as application/octet-stream, so Invoke-WebRequest(...).Content is a byte[], and
-split on a byte array splits its decimal values. Fix: save it with -OutFile and read it
back with Get-Content -Raw.
rustup-init.exe : info: profile set to minimal
+ FullyQualifiedErrorId : NativeCommandError
rustup writes info: lines to stderr. With $ErrorActionPreference = "Stop", PowerShell 5.1
turns the first stderr line of a native command into a terminating error. The install had
already started, so this left a half-installed toolchain. Fix: set the preference to
Continue around that one call and judge success by $LASTEXITCODE. Re-run with -Force.
The third run printed:
rustc 1.98.1 (48a229cea 2026-09-01)
cargo 1.98.1 (797e8a9bc 2026-08-05)
-gnuThe default Windows host is x86_64-pc-windows-msvc, which links with link.exe from the
Visual Studio Build Tools. Those need admin to install. The x86_64-pc-windows-gnu host
links with MinGW, and rustup ships a MinGW linker in the toolchain itself:
~/.rustup/toolchains/stable-x86_64-pc-windows-gnu/lib/rustlib/x86_64-pc-windows-gnu/bin/self-contained/
GCC-WARNING.txt dlltool.exe ld.exe libwinpthread-1.dll x86_64-w64-mingw32-gcc.exe
GCC-WARNING.txt says that gcc.exe "cannot be used for compiling C files - it is only
used as a linker". So it cannot replace MSYS2's gcc for the C track. The two coexist
without conflict.
The OCaml track hit a Device Guard policy that blocks flexlink.exe. The worry was that
the same policy would block this linker. It did not. The first cargo build --release
finished in 2 seconds.
One oddity: immediately after a rebuild, running the new logagg.exe failed once with
Permission denied. Two seconds later the same command worked. The likely cause is
Defender scanning a freshly linked executable; it has not recurred, and verify.py has not
hit it. If a verify run ever fails that way, re-run before debugging.
$ cargo build --release
Finished `release` profile [optimized] target(s) in 2.01s
$ ./target/release/logagg.exe ../../bench/data/access.log > out.txt; echo rc=$?
rc=4
$ cmp out.txt ../../bench/expected/logagg.txt && echo serial-identical
serial-identical
Identical on the first run. That is less impressive than it sounds: this is the seventh
track, and SPEC 2's integer arithmetic removes the usual source of disagreement. The things
that broke the first diff in earlier tracks (CRLF on stdout, %-20s not truncating) are
handled by default in Rust. Stdout is not translated, and {:<20.20} truncates.
cargo clippy -- -W clippy::pedantic found two things:
use std::fmt::Write as _; declared inside render, after statements. Moved to the top..and_then(|_| stdout.flush()) on a Result<()>: pedantic prefers |()|, which states
that the ignored value is the unit type.cargo fmt then expanded split6's one-line array of six it.next()? onto six lines. It
reads worse. rustfmt, like gofmt, is not a matter of taste, so it stays.
The README says Rust rejects the C track's bugs. These are the programs and the actual
output from rustc 1.98.1. Each file is under 20 lines and was compiled with
rustc --edition 2024.
let mut services: HashMap<&str, u64> = HashMap::new();
let mut buf = String::new();
loop {
buf.clear();
if reader.read_line(&mut buf).unwrap() == 0 { break; }
let service = buf.split_ascii_whitespace().nth(1).unwrap_or("");
*services.entry(service).or_default() += 1; // the C track's bug, typed
}
error[E0502]: cannot borrow `buf` as mutable because it is also borrowed as immutable
--> bug.rs:9:9
|
9 | buf.clear();
| ^^^^^^^^^^^ mutable borrow occurs here
10 | if reader.read_line(&mut buf).unwrap() == 0 { break; }
11 | let service = buf.split_ascii_whitespace().nth(1).unwrap_or("");
| --- immutable borrow occurs here
...
14 | println!("{}", services.len());
| -------- immutable borrow later used here
It reports the error at buf.clear(), not at the entry call. The mistake is storing the
key; the error appears where the stored borrow collides with the next mutation. Reading
from the bottom up (line 14, then 11, then 9) is the useful direction. That tells you
where the borrow is still alive and why.
A second E0502 follows for read_line(&mut buf), same cause.
thread::spawn instead of thread::scopelet text = std::fs::read_to_string("access.log").unwrap();
let lines: Vec<&str> = text.lines().collect();
let handles: Vec<_> = lines
.chunks(50)
.map(|chunk| thread::spawn(move || count(chunk))) // no scope
.collect();
error[E0597]: `text` does not live long enough
...
10 | .map(|chunk| thread::spawn(move || count(chunk))) // no scope
| ----------------------------------- argument requires that `text` is borrowed for `'static`
...
13 | }
| - `text` dropped here while still borrowed
'static means "for the rest of the program". spawn demands it because a spawned thread
may outlive the function that started it. The two usual fixes are to clone the data into
each thread or wrap it in Arc. thread::scope removes the demand instead.
let mut services: HashMap<String, u64> = HashMap::new();
thread::scope(|s| {
for _ in 0..2 {
s.spawn(|| *services.entry("api".into()).or_default() += 1); // one shared map, no lock
}
});
error[E0499]: cannot borrow `services` as mutable more than once at a time
In C this compiles and is undefined behaviour. The Rust fix is either a Mutex around the
map or a map per thread. logagg uses a map per thread.
--shard NNo earlier track implemented SPEC 6. Here it is about 33 lines: tally_sharded and
Tally::merge. The check:
$ for n in 1 2 3 4 7 16 64 500; do
> ./target/release/logagg.exe --shard $n access.log | cmp - logagg.txt && echo "shard $n identical"
> done
shard 1 identical
...
shard 500 identical
500 is more shards than the file has lines (200). lines.len().div_ceil(500) is 1, so it
runs 200 workers with one line each, which exercises the merge more than any realistic
count. The line numbers in the malformed: block stay right because each worker numbers
its lines from chunk_index * per + 1, and the merge appends the workers' lists in chunk
order.
verify.py does not run the shard path. It tests the contract every track shares. If a
second track implements --shard, that is the time to add it to the verifier.
Three edge cases the fixture does not reach. Recorded so they are not rediscovered:
split_ascii_whitespace splits on space, tab, LF, FF, and CR. Python's
str.split() also splits on vertical tab (\x0b) and the ASCII separators \x1c–\x1f.
A line containing \x0b between fields would be FIELDS here and good in Python.i64 of microseconds. Python's integers
are unbounded. This version reports LATENCY for that line; it does not panic or wrap.read_line returns an InvalidData error on bytes that are not
UTF-8, and logagg reports it as fatal (RC 12, nothing on stdout). Python opens the file
as ASCII and raises UnicodeDecodeError, which is not an OSError, so the reference
crashes with a traceback. Neither is in the spec. The Rust behaviour is the better one.entry() allocates on every line. The common idiom is
self.services.entry(rec.service.to_owned()).or_default().add(...). It builds a new
String for every good line (191 allocations for six services), because entry takes
the key by value. logagg looks up with get_mut(&str) first and inserts only on a
miss: four more lines, six allocations. hashbrown, the
map behind std's, has entry_ref for this. The standard library does not expose it.std::io moved into alloc. Looking for read_line's implementation for Q9,
library/std/src/io/mod.rs turned out to re-export most of it from
library/alloc/src/io/. GitHub code search found it in read.rs.