For some historical context: In very early versions of Rust, the equivalent of String was ~str, the equivalent of Vec<T> was ~[T], and for non-slice types, the equivalent of Box<T> was ~T.
| Value |
Borrowed pointer |
Owned pointer |
u8 |
&u8 |
~u8 |
str |
&str |
~str |
[T] |
&[T] |
~[T] |
This made the type names more consistent, but their behavior less consistent. For example ~str and ~[T] were growable, just like modern String and Vec, so they had special built-in methods that were not available on their borrowed equivalents. They were also 3 pointer-widths in size, which was inconsistent with other ~T types. And there could be no equivalent of modern Box<str> or Box<[T]>.
Today we have have reduced the number of special built-in pointers, and added library types in their place:
| Slice |
Borrowed pointer |
Owned pointer |
Growable buffer |
str |
&str |
Box<str> |
String |
[T] |
&[T] |
Box<[T]> |
Vec<T> |
Path |
&Path |
Box<Path> |
PathBuf |
OsStr |
&OsStr |
Box<OsStr> |
OsString |
| …etc |
|
|
|
This reduces language complexity but moves the same complexity into libraries, and it means fewer sigils to remember but more names.
These library types may become more understandable if you consider them all “smart pointer” types. For example String implements Deref<Target=str>, so when you access its contents the relevant type is still str.