This article is written for Rust programmers learning C. As someone
in the opposite position I know C and I've been curious about Rust I'd love to read the reverse.
What's the hardest thing for a C programmer to unlearn when moving
to Rust? Is it the borrow checker, or is that just the thing people
talk about because it's the most visible?
In C, mostly you think about your code running on the machine with some thought dedicated to the compiler gymnastics involved during the compilation phase.
With Rust, you are front loaded with a myriad of compiler gymnastics you need to think through.
But once you get comfortable enough, it becomes natural. You also have to get accustomed to writing code that is more verbose than C which might look ugly at first but later you start to accommodate it as the necessary cost for the utility you are handed in return by the compiler.
For example, multiple variables in Rust doesn't necessarily mean multiple memory allocated variables at runtime like in C. The rust compiler will usually keep track of and ensure multiple variables (non Copy types such as String with Move semantics) map to one memory allocated variable at runtime (in normal single threaded use cases under normal circumstances without using RC, ARC, etc.). Eg: let a = String::from("hello"); let b = a; ... Note: The example is for illustration purposes only and not always true. In summary source code variables are abstractions and may not belong to distinct runtime memory location. Yes it is true even for C. But Rust's ownership model makes that distinction aggressively visible.
I'll offer a thought, which amounts to rephrasing "front loaded with a myriad of compiler gymnastics you need to think through." from sibling.
There's a _lot_ of utility in things that aren't represented by a literal difference in the compiled code. It's not unusual to make a type in Rust that's just a single field; you're not making a struct to gather related data together, you're giving it a different name because the same data means something different.
It's a little like the difference between a uintptr_t and a uint16_t*. They're represented the same in the machine, the difference only exists in the source language.
Rust derives a lot of value from things that only mean something at compile time. I think you'd "unlearn" what C (commonly) teaches when you don't find a C struct with one member "weird".
> What's the hardest thing for a C programmer to unlearn when moving to Rust?
One thing that stood out to me going this direction is it's really hard to write hackish or intentionally-broken code in order to try to explore implementation options or debug an issue or test an algorithm. Rust requires you to handle error cases, and all possible values in cases, and is strict about memory access and stuff. But sometimes I just want to shove these bytes into this thing and see what happens, or hack in an early return or something, or half-implement this API to see if it "feels right" before committing to a full implementation, and Rust's enforced best practices stop me doing that.
(To be clear, I like Rust. This is just one thing that trips me up coming from C.)