I am trying to better understand how Rust allocates memory, and how much does it allocate.
let x = 10;
defaults to i32 and so Rust allocates 4 bytes on the stack.
let x = String::from("The black cat on a green tree");
Rust creates the variable x on the stack that contains the len, capacity and a ptr to the buffer where the actual content is. If I am not mistaken the size the stucture on the stack is 2*usize to store the len and capacity but I don't know how much does the ptr take up. Is it usize as well? (See from_raw_parts).
The buffer takes up the number of bytes needed to store the content. "The black cat on a green tree" in our example. Rust might also allocate some more space which is the difference between capacity and len.
let x = vec![1, 2, 3]
Rust creates x on the stack containing capacity, len, and ptr just as with the strings.
The ptr points to some place in the memory where rust allocated 3*4 bytes for the 3 i32 numbers. Rust might have allocated some (capacity-len) to allow for growth without relocation.
let x = vec![String::from("cat"), String::from("dog")];
x on the stack as usual containing len, capacity and ptr
ptr points to the heap where it allocated 2 places to hold the len-capacity-ptr triplets for each of the 2 strings. The ptrs in these two places contain the address of the strings. (so in this case rust needs to follow 2 pointers from x to reach the the cat.
Is this an accurate description? If not is there a better explanation? BTW there is a drawing here that, as I understand, covers the same structure as we have in the i32 case.
usize is defined to have the same size as a pointer.[1] So, String and Vec will generally have the size of “three pointers”. (This is not guaranteed to be true, but it can't be smaller, and making it bigger would be wasteful.)
Is this an accurate description?
Yes, you've got it right, including the placement of Strings in a Vec. Many people find that confusing!
How much memory do the pointers take up?
When people talk casually about a “32-bit” or “64-bit” CPU architecture, they're talking about the size of pointers. On the platforms supported by Rust, pointers may be 16, 32, or 64 bits (2, 4, or 8 bytes).
Today, most general-purpose computers are 64-bit.
A couple of decades ago, 32-bit computers used to be very common, but today the most likely place you'll encounter it is writing Rust code that compiles to WebAssembly, which is currently only 32-bit. 32-bit systems can address 4 GiB of memory, which is quite substantial but can be limiting when loading large data files.
16-bit architectures can only address 65536 bytes of memory, so their use is very limited and you'll never be programming one unknowingly. They are generally microcontrollers or obsolete computers.
Can you say more about the eventual goal from this? Because "let x = 10; allocations 4 bytes on the stack" is first-order correct, but if you look deeper you'll find that it often doesn't allocate stack memory at all.
Optimizations. The high-level notion of what allocates, where, and when, can be substantially affected by optimizations. The compiler will aggressively try to promote the stack allocation of small, trivial values (such as a single integer or a struct containing nothing but a single primitive value) to registers, or remove them entirely if possible. This is because accessing registers is very fast compared to accessing actual operative memory (think orders of magnitude).
If you are purely interested in the operational semantics of the language, then your description above is correct. It is important to understand, though, that whatever the abstract machine is said to do does not necessarily imply what actual machine code will be emitted – and even the behavior of the machine code is not necessarily what the CPU will actually do, again due to complex just-in-time optimizations and execution plans performed by modern CPUs.
Most microcontrollers these days are also 32-bit, at least most that you are likely you encounter as a hobbyist. That of course doesn't mean they will have the full 4 GB of memory (very unlikely).
I generally don't find it that useful to have a model for stack allocations. You can, the vast majority of the time, just treat them as free and use them. And if there are exceptions, it's for big things like MaybeUninit<[u8; 1<<10]>, not for a couple of usizes.
Heap allocation reuse is important, and it's thus important to pay attention to things that allocate (and deallocate). But for stack stuff, the answer is usually just to not think about it.
If you actually want to figure out whether something will make a stack allocation or not, you're going to have to dive deep into the details of all the optimizations that rustc and LLVM will do, and then that'll all change again in another six weeks.