Chapter 01
Your dotnet muscle memory transfers almost completely. The big mindset shift isn't the CLI — it's learning to treat the compiler as the strictest, most helpful code reviewer you've ever had.
Rust ships as a toolchain managed by rustup (think of it as a version manager for the SDK). The day-to-day driver is cargo, which is the dotnet CLI, NuGet client, and build system rolled into one binary.
| You'd type in .NET | In Rust | What it does |
|---|---|---|
dotnet new console | cargo new app | scaffold a project |
dotnet build | cargo build | compile |
dotnet run | cargo run | build + run |
dotnet test | cargo test | run tests (built into the language) |
dotnet add package X | cargo add x | add a dependency |
dotnet build -c Release | cargo build --release | optimized build |
| Roslyn analyzers | cargo clippy | lints, but far chattier and usually right |
dotnet format | cargo fmt | canonical formatter (no debates) |
A crate is the unit of compilation — closest to a .NET assembly. A module (mod) is an in-crate namespace, like a namespace block. crates.io is NuGet. Cargo.lock is your packages.lock.json, but on by default for apps.
Project metadata lives in Cargo.toml (TOML, not XML). Here is this very server's manifest, annotated:
Cargo.toml# like <PropertyGroup> in a .csproj
[package]
name = "rust-for-csharp"
version = "0.1.0"
edition = "2021" # ~ <LangVersion>
# each line ~ <PackageReference Include=".." Version=".." />
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
That features array has no clean C# analogue. Crates ship with optional, compile-time feature flags so you only pull in (and compile) the parts you use — closer to MSBuild conditional compilation symbols than to NuGet.
using System;
class Program {
static void Main() {
Console.WriteLine("Hello");
}
}
fn main() {
println!("Hello");
}
Two things to notice. println! ends in ! because it's a macro, not a method — it does compile-time work (format-string checking) that a regular function can't. And there's no class wrapper: Rust has free functions, like top-level statements but everywhere.
This is the cultural shift. In C# you lean on the runtime and tests to catch mistakes; an exception at runtime is normal. In Rust, an enormous class of bugs is simply rejected at compile time — and the error messages are written to teach, often suggesting the exact fix.
Expect to fight the compiler for a week or two, then stop noticing it. "If it compiles, it works" is overstated, but the category of thing that compiles-and-then-corrupts-memory or throws a NullReferenceException largely disappears. You're trading runtime surprises for compile-time friction.
Next we tackle the reason that's possible — the one idea with no C# counterpart.