Every language promises something.
JavaScript promises that objects just work. Go promises simplicity. C promises control. C++ promises performance.
But there's one promise every language makes that none of them keep: "Your data won't change when you don't expect it to."
Rust is different. Rust keeps that promise. And the way it does it is what makes Rust feel scary at first — but also what makes it powerful.
The Promise Every Language Breaks
Think about what you want when you pass data to a function:
- You want to know if it can be changed. (Can this function mutate my data?)
- You want to know if your data is safe. (Can this function cause a memory leak?)
Every language fails at #1. Let me prove it.
JavaScript Can't Promise Safety
In JavaScript, there's no way to know if a function will change your object:
function process(obj) {
obj.value += 1; // Does this mutate? Who knows!
}
You'd need to read the function body. And even if you do, the function might change later. Or call another function that changes things. Or use Object.freeze() which is easily bypassed and doesn't work recursively.
Go Can't Promise Safety
Go has the same problem. Pass a struct to a function and you get a copy — unless you pass a pointer. But here's the catch: there's no way to know from the signature whether a function will mutate your data.
func process(o O) { // Copies o - safe?
func process(o *O) { // Modifies original - dangerous?
You can't tell from the function signature. You have to read the implementation. And even then, does the implementation call other functions? Do those mutate? You can't know.
C and C++ Can't Promise Anything
In C/C++, you can mark something const:
void pinky_promise_i_wont_mutate_u(const S& s) {
S* s2 = const_cast<S*>(&s); // HAHA JOKE'S ON YOU
s2->i += 1; // Mutated anyway!
}
const_cast just... removes const. There's no enforcement. The compiler can't stop you.
What Rust Actually Delivers
Rust makes promises it can keep. Here's the same code in Rust:
fn show(s: String) {
println!("{}", s);
}
fn main() {
let s = String::from("hiya");
show(s); // s is moved here
show(s); // ERROR: s was already moved
}
That second line is a compile error. Not a runtime error. A compile error. The compiler is telling you: "You already gave away ownership of s. You can't use it again."
This isn't a limitation. This is the feature.
The Promise of Ownership
When you pass a String to a function in Rust, you have two options:
- Move it — give ownership to the function. The function is now responsible for cleaning it up.
- Borrow it — let the function use it without taking ownership.
There's no hidden third option. No "maybe it copies, maybe it doesn't." No "I'll freeze this object" that doesn't really work. The signature tells you everything:
fn process_owned(s: String) { /* takes ownership */ }
fn process_borrowed(s: &String) { /* just looks at it */ }
That's it. That's the promise.
The Promise of Immutability
Here's where it gets beautiful. When you borrow something in Rust, it's automatically immutable:
struct Conn { name: String }
fn get_conn_name(c: &Conn) -> &str {
&c.name // Can't mutate - it's a shared reference
}
fn main() {
let conn = Conn { name: String::from("prod-db") };
println!("{}", get_conn_name(&conn));
println!("{}", get_conn_name(&conn)); // Works fine - shared reference
}
Try to mutate through a shared reference:
fn get_conn_name(c: &Conn) -> &str {
c.name = String::from("hacked!"); // ERROR: cannot assign to c.name
&c.name
}
The compiler says: "No. It's a shared reference, so the data cannot be written."
This isn't a lint warning. This isn't a style suggestion. This is a compile error. You cannot mutate through a shared reference. Period.
Why This Matters
This isn't academic. Think about real code:
- Database connections — you want to know if a function will close your connection
- File handles — you want to know if a function will close your file
- HTTP requests — you want to know if a function will consume your request body
In other languages, you need to read every function's implementation to know these things. In Rust, you know from the signature.
fn close(conn: Conn) { /* takes ownership, will close */ }
fn get_name(conn: &Conn) -> &str { /* just reads */ }
The type system is telling you everything. That's the promise.
The History Behind the Promise
Rust didn't invent this. The concept is called linear types, and it's been around since 1990 when Philip Wadler wrote "Linear types can change the world!"
Languages like Clean, Haskell, and Pony have explored similar ideas. But Rust is the first mainstream language to make it practical.
The key insight: if you know exactly who owns a value, you know exactly when it's cleaned up. No garbage collector needed. No reference counting. Just compile-time certainty.
What This Means for You
When you're learning Rust, that "use of moved value" error feels annoying. Why can't I just pass this string twice?
But that error is Rust keeping its promise. It's saying: "You already gave this away. I won't let you use it again, because that would be a bug."
The scary part — ownership, borrowing, the borrow checker — is exactly what makes Rust work. It's not a hurdle to overcome. It's the feature.
The Promise
Every language promises:
- Your data won't change unexpectedly
- You won't have memory bugs
- You'll know who owns what
Only Rust keeps that promise at compile time. The borrow checker isn't fighting you. It's protecting you.
The part that makes Rust scary is the part that makes it unique. And it's also what you'll miss in every other language once it clicks.
This post was inspired by Amos's "The Promise of Rust" at fasterthanli.me — go read the original for even more depth on how Rust compares to C, C++, Go, and JavaScript.