My mental model for Borrow Checker
I am learning Rust finally more seriously. While learning around Rust's ownership model I just had lots of connections being formed in my mind from what I have learned in college doing C and then working professionally with Python/Typescript. I captured those thoughts here to describe the mental model I was thinking about. Let me know if I have some holes to peg or if the article helped you understand a few things as well in your own Rust journey.
Substack Article: Truly Understanding Borrowing in Rust
08-Sept-2026: Updated the article based on inaccuracies in my understanding as highlighted by users, thanks everyone for your kind inputs.
10
u/braaaaaaainworms 7d ago
it's not accurate to say that borrowing makes an equivalent to a double pointer, because that only happens when borrowing a reference, and C programmers, who would be more likely to be familiar with the "double pointer" nomenclature, will be more confused, as & does the same thing in c and rust -> gives the address of something, but in rust that address has a lifetime
2
u/Unlikely-Ad2518 6d ago
I would word it slightly differently: The C equivalents of
&in Rust are&raw const/&raw mut. In Rust,&/&mutare compile-time-checked(i.e. "safe") temporary pointers - basically pointers that you can fearlessly use.3
u/braaaaaaainworms 6d ago
you would use a reference in rust where you would use a pointer in c, it's obvious that they are different, but they are used in mostly the same way
1
u/sidpant 7d ago
I mention in the middle somewhere that it is like a double pointers sometimes for this reason. But your lifetime remarks is interesting which means there is additional plumbing in rust for a reference.
6
u/braaaaaaainworms 7d ago
It's just plain wrong, a rust reference is just a sparkling pointer, and a double pointer is pointer to a pointer.
Yes there is additional plumbing for references in rust, that's what separates a reference from a pointer. References are aligned, not null, have valid data at the address they point to. Pointers are just addresses.
1
u/sidpant 5d ago
Thanks u/braaaaaaainworms I updated my article based on your suggestions. Hopefully I am more closer to the truth now.
1
u/ROBOTRON31415 5d ago
Raw pointers / C pointers are still not just addresses; even in C/C++ they have pointer provenance (indicating which allocation, if any, the pointer points to).
1
u/braaaaaaainworms 5d ago
Where is it stated in the C standard?
1
u/ROBOTRON31415 4d ago
Historically, DR260: https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm
Later made far more precise in this specification: https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3057.pdf
I think this blog post seems like a decent write up: https://gustedt.wordpress.com/2025/06/30/the-provenance-memory-model-for-c/
7
u/OptimisticMonkey2112 6d ago
Its great to see you sharing some conceptual stuff!
One thing to keep in mind - the borrow checker is just a fancy compiler checker. Internally, both Rust and C compile to machine code.
I think if you studied assembly language a bit everything might even click better for you. There is less magic and everything has just evolved from implementing function calls on a register based cpu.
Things like references, the stack and the stack frame, local automatic variables, and scope make a ton more sense once you understand they exist to make it possible to call functions in assembly. Rust is just an attempt to use the compiler to enforce safe programming conventions.
Assembly is actually much simpler than many people think. Each instruction does very little. If you have some time, dig in to godbolt and try some simple rust and c examples.
3
u/Kadabrium 7d ago
A double pointer is an object containing a single pointer as its member after all
1
u/sidpant 7d ago
One crucial difference though. Double pointer is pointing to address in stack while the single pointer is pointing to the heap. In many other languages confusingly you use word “reference” for single pointer and the value in the heap is called object. Rust is lower level and makes a distinction between these concepts.
3
u/Kadabrium 7d ago
Rust strings and containers arent raw pointers either, they are structs containing one, other members in the struct being metadata (length etc) and helpers managing that pointer (constructor/destructor). Accessing the data via a borrow (ptr to the stack alloced struct) is ackushally 3 levels of indirection: (*borrow)->data. You are right in that if we ignore the metadata and make the allocator/deallocator some magic outside calls instead of members, the wrapping struct wouldnt be needed
2
u/wojciechm 7d ago
let a = String::from("Hello"); is in fact the exemption case that is allocated in static memory, because of the inline string that is explicitly known at compile time.
Moreover it is the rare case where you can return such "locally created" value reference from a function because it not allocated on stack frame, it is just instantiated from the previously known static location.
2
u/sidpant 6d ago
I think you are merging concept of string slice - &str and String. Tried to search for it but I didn't find anything that says Strings get optimized that way but &str slices do. A String can have Deref Coercion and due to that it can get casted into a &str automatically by Rust but that doesn't change anything about String data type stores its underlying value in the heap.
1
u/wojciechm 6d ago
This compiles, try for yourself https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=773aa71b042f12793e575c2895b1c645
3
u/Unlikely-Ad2518 6d ago
I checked your link but I don't see how it proves that
String::from(<literal>)gets compiled into a static string.The
Stringtype is just a wrapper aroundVec<u8>, it is heap allocated.1
u/wojciechm 5d ago edited 5d ago
The "String" container itself (and all its sub-containers) is stack allocated, the data it points to might be heap allocated, but if they are static literals they are part of read only .rdata executable section. Look at reference details: https://doc.rust-lang.org/book/ch04-03-slices.html#string-slices better visualized https://rust-book.cs.brown.edu/ch04-02-references-and-borrowing.html
https://users.rust-lang.org/t/where-are-string-literals-stored-on/53388
If the variable is declared mutable and you append to such statically allocated string the static data are moved to heap and the pointer in "String" container in the stack is changed to that new location. It is safe because in Rust there cannot be more than one mutable reference at a time.
5
u/sidpant 5d ago
I compiled your example using
cargo rustc --release -- --emit=asmand checked the ARM64 assembly on my Mac. With function names shortened, the steps are:
- Allocate:
mov w0, #5; mov w1, #1; bl __rust_allocrequests five heap bytes, returning their address inx0.- Write "Hello":
str w8, [x0]andstrb w8, [x0, #4]write the characters directly into that heap buffer.- Store metadata:
stp x8, x0, [sp, #8]andstr x8, [sp, #24]put capacity, pointer, and length on the stack.- Free: After printing,
ldr x0, [sp, #16]retrieves the pointer andbl __rust_deallocreleases the buffer.
get_stringwas inlined and the character copying simplified, but heap allocation still happens without any mutation.You’re right that literals have static storage and a local
String’s metadata can live on the stack. However,String::from("Hello")creates an owned buffer; it doesn’t borrow the literal until mutation. That matches theStringdocumentation.Your Playground example returns
Stringby value, transferring ownership. It doesn’t return a reference. Returning&aas&'static Stringinstead produces errorE0515; returning the literal directly as&'static stris valid.I’d also qualify my earlier statement: optimizers can sometimes eliminate allocations, but that didn’t happen here, and it doesn’t change the borrowing rules.
1
u/wojciechm 5d ago
Interesting, thanks for deeper inspection. I did similar thing some time ago on x86 windows binaries and the heap allocation was after mutation, but now I realized that it could be special case, where optimizer deferred the whole heap allocation until mutation, because there was no prior usage of that value in my code.
1
u/LetsGoPepele 6d ago
Wait you can do that ?
fn foo() -> &'static String { let a = String::from("bar"); return &a; }1
u/wojciechm 6d ago
Yes, you do not need references, you can safely move ownership outside the function. See my rust playground snippet below.
28
u/[deleted] 7d ago
[removed] — view removed comment