You might have heard of Sized
before. A sized type is one that always has the same size in bytes (and this size is known to the compiler). This is very important to the compiler, because if some_variable
is 4 bytes large, it knows the variable after some_variable
is stored 4 bytes later, which means it can hardcode that four in the compiled program. You can only store a variable on the stack if it is sized.
Now, how many bytes is a string? Well it depends on the length. "abc" is three bytes, while "hello world" is 11. So str
is not Sized
, which means you cannot put it on the stack. Luckily a reference &str
is always the same number of bytes (usually 4 or 8 depending on the computer). So storing a reference on the stack is fine.
When you type
let s = "Hello world";
the compiler will place the bytes H
, e
, l
, ... somewhere in the executable, and in your function it will hardcode a reference to that location. This is why s
has the type &str
. You cannot put a str
in a variable at all, because variables are stored on the stack.
The situation is the same as [T]
vs &[T]
, where [T]
is just some number of T
s after each other, thus having variable length, while &[T]
is a pointer (of 4 or 8 bytes) that points to somewhere with the T
s. Do note that there is an [T; n]
type, where you hardcode the number of elements. As an example, a [i32; 8]
has a known size and can be stored on the stack.
let a: [i32; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
However, you will not be able to resize a [i32; 8]
, because the 8
is part of the type. In Hyeonu's example you can see that you could store a string in a [u8; 10]
, although you should note this means you can only have strings of length 10, while a &str
can point to a string of any length.