Journal
Progress
Section titled “Progress”- Lesson 1 — Toolchain and Cargo
- Lesson 2 — Types, mutability and expressions
- Lesson 3 — Ownership and moves
- Lesson 4 — Borrowing and strings
- Lesson 5 — Structs, enums and pattern matching
- Lesson 6 —
Option,Resultand? - Lesson 7 — Traits and generics
- Lesson 8 — Collections and iterators
- Lesson 9 — Lifetimes
- Lesson 10 — Modules, crates and workspaces
- Lesson 11 —
Box,Rc,Arc,RefCell - Lesson 12 — Threads,
Send/Sync,Mutex, rayon - Lesson 13 —
asyncand tokio - Lesson 14 — Tests, docs, clippy, fmt
- Lesson 15 — Macros,
unsafeand FFI
2026-09-13 — Lessons 1 to 4
Section titled “2026-09-13 — Lessons 1 to 4”- Toolchain on my machine:
rustc 1.94.0,cargo 1.94.0, edition 2024 by default forcargo new. - Every snippet was compiled before being written into a lesson; the compiler errors are copied from real
rustcoutput (only the long “other types implement this trait” notes were trimmed). - The course code lives in
code/rust-for-csharp-java: runnableexamples/andcompile_faildoctests insrc/lib.rs, checked by the Rust course examples GitHub workflow.
Surprises coming from C#:
- Integer overflow panics in debug builds but wraps in release builds — verified with
255u8 + 1compiled both ways. C# wraps in both unlesschecked. - Clippy flagged
let scores = vec![90, 72, 85, 60];asuseless_vecbecause the vector was only ever read as a slice — an array would do. - The borrow checker error for “push while iterating” is the compile-time version of
InvalidOperationException: Collection was modified.
Answered: does compile_fail,E0382 check the error code? Not on stable. A doctest marked compile_fail,E0999 around a use-after-move (really E0382) still passed with cargo test --doc on 1.94.0 — stable only checks that compilation fails. The CI workflow now also runs cargo +nightly test --doc, which does compare the codes.
2026-09-13 — Lessons 5 to 8
Section titled “2026-09-13 — Lessons 5 to 8”Every exercise solution is now a doctest too: 41 doctests in total (compile-fail snippets plus solutions), all green on 1.94.0.
Mistakes the tests caught before publishing:
- I first wrote
prices.sort_by(|a, b| a.total_cmp(b))onvec![19.99, 5.0, 12.5]. It does not compile: the literals are still an undecided{float}when the closure is type-checked (E0599: no method named total_cmp found for reference &{float}). AnnotatingVec<f64>fixes it — lesson 8 now explains this. - In lesson 7 I claimed
cheapestcould take aVec<Box<dyn Priced>>with aT: Priced + ?Sizedbound. Wrong:&[T]requiresT: Sized. The working version implementsPricedforBox<dyn Priced>, and that is what the exercise now shows (and tests). - Clippy rejected
(1..=5).fold(1, |acc, n| acc * n)in favour of.product()(unnecessary_fold), so thefoldexample computes a min/max pair instead — something no single adapter does.
Surprises coming from C#:
- When
mainreturnsErr, Rust prints the error with itsDebugformat (Error: Missing("port")), notDisplay, and exits with code 1. Vec<f64>::sort()does not compile at all (f64is notOrd), where C# and Java sort doubles silently.- The
E0004message for a new enum variant names the exact missing pattern (&Payment::Crypto { .. } not covered) — better than any C# analyzer warning I know.
2026-09-13 — Lessons 9 to 12
Section titled “2026-09-13 — Lessons 9 to 12”76 doctests now (stable and nightly), plus a real two-crate workspace for lesson 10 (code/rust-for-csharp-java/l10-workspace) that CI tests, lints and runs. The course crate gained its first dependency: rayon, as a dev-dependency.
Things I got wrong first:
- In the lesson 9 example I used
drop(parser)to show that the tokens outlive the parser. Clippy refused it (drop_non_drop): dropping a type with noDropimpl does nothing useful. Atokenizefunction whose local parser dies at the end makes the same point better. - Clippy also taught me
u64::is_multiple_of(manual_is_multiple_of) instead ofn % d != 0. - I assumed a rayon
mapthat mutates a captured counter would fail with aSend/Syncerror. It fails earlier: rayon’s closures areFn, so it isE0594: cannot assign to a captured variable in a Fn closure.
Surprises:
- The
RefCellpanic message on 1.94 is justRefCell already borrowed; older material quotesalready borrowed: BorrowMutError. - Writing
&strinstead of&'a stras a method’s return type compiles fine — the error only appears at the call site, when you try to hold two tokens (E0499). Elision picked&mut self’s lifetime. - A recursive enum without
BoxgivesE0391(a cycle in the compiler’s “needs drop” query) in addition toE0072. - rayon on this machine (Core Ultra 9 285K, 24 cores): counting primes below 5,000,000 went from ~775 ms to ~41 ms, about 18×.
2026-09-13 — Lessons 13 to 15, and three operating systems
Section titled “2026-09-13 — Lessons 13 to 15, and three operating systems”The core course is complete. The code now has tokio as a dev-dependency, two more small crates (l14-testing, and l15-ffi with a .NET 10 client), and CI runs everything on Windows, Ubuntu and macOS. The C# program calls the Rust library successfully on all three.
Things I got wrong first:
- The lesson 15 FFI test asserted
pricing_sum([19.99, 5.0, 12.5]) == 37.49. The realf64sum is37.489999999999995; C# had printed37.49only because of itsF2format. - An XML comment in the
.csprojcontained--release: MSBuild refuses to load a project whose comment contains--. - I wrote that
[LibraryImport]marshalsboolas a 4-byteBOOLby default. It has no default at all: the build fails withSYSLIB1051until you add[MarshalAs]. That was[DllImport]’s behaviour. cargo fmt --check --manifest-path …worked on my machine (Cargo 1.94) but failed on the CI runners (Cargo 1.98.1) withFailed to find targets. CI now runscargo fmt --checkfrom each crate’s folder.missing_docs = "warn"in[lints]also applies to integration tests: each file intests/is its own crate and needs a//!line.- A lesson 13 doctest asserted that three concurrent sleeps finish within 290 ms. That is true on my machine but a timing assertion is a flaky test on shared CI runners, so the test now checks only the results.
- Even without a timing assertion, a delay is still a race: on a busy macOS runner, a 200 ms attempt “succeeded” despite a 50 ms
timeout(when the runtime wakes up after both deadlines,timeoutpolls the future first, and it is already ready). Exercise 2’s slow attempts now last 5 seconds; the test stays fast because they are abandoned after 50 ms.
Surprises:
- In async Rust, a forgotten
.awaitmeans the code never runs — the opposite of C#, where the task starts anyway. - The “future cannot be sent between threads safely” error has no
Ecode: it comes from theSendbound ontokio::spawn, not from the language. - Edition 2024 requires
unsafe extern "C"and#[unsafe(no_mangle)]; much older FFI material no longer compiles as written. cargo fmthad never been run on this course: 30 formatting differences in twelve lessons of examples.- Hello world in release mode: about 130 KB on Windows, 430–460 KB on Linux and macOS (CI runners, Cargo 1.98.1).
Open questions
Section titled “Open questions”- How does
rust-analyzerin RustRover compare with VS Code for these exercises?