The difference is very minor when interoperating with methods, but the performance gains of this dual string system are often worth it.
&str is basically a C string allocated on the stack while String is like a Java string, an object on the heap with a reference to a raw string hidden from plain sight. To avoid unnecessary and unintended allocations and other expensive memory operations, operating on &str is usually preferred for performance reasons.
String almost transparently casts down to &str so in practice you rarely care about the difference when calling library code.
If you're coming from a language that doesn't have a distinction between character arrays and string objects, you're probably fine just using &str.
If you're coming from a higher level language like JS or Python, you're probably used to paying the performance price for heap allocation anyway so you might as well use String in Rust as well and only start caring when performance is affected.
&str doesn’t mean stack-allocated. It’s just a pointer [0] (and a len) to a section of memory that’s (required to be) legal utf-8.
A &str can point at stack memory or heap memory (usually the latter, since it’s common for them to point to a String, which allocate on the heap), or static memory.
But yeah, String keeps things simple, and when in doubt just use it… but if you want to understand it more, it’s better to think of who “owns” the data.
Take a String when you need to build something that needs to own it, like if you’re building a struct out of them, or store them in a hash map or something. Because maybe a caller already “owns” the string and is trying to hand over ownership, and you can avoid the clone if it’s just passed by move.
If you’re only using the string long enough to read it and do something based on it (but don’t want to own it), take a &str, and a caller can be flexible of how it produces that (a &'static str, a String ref, a substring, etc.)
The example that always works for me as a way to remember is to think of HashMap.
HashMap.get takes a reference for the key (analogous to &str), because it’s only using your reference long enough to compare to its keys and see which one matches.
HashMap.insert takes a value for the key (analogous to String) because it needs to own the key and store it in the table.
HashMap.insert could take a reference, but then it’d have to clone it, which means you’d miss out on the opportunity to more cheaply move the key (which is a simple memcpy) instead of calling clone() (which often does more calls to clone and can be complicated)… and only would support clone able keys.
[0] yeah yeah, a reference, not a pointer, but the point is it “points to” a place in memory, which may be heap, stack, static, anything.
The difference between a heap allocated string (String), a static string literal embedded in the binary (&str), and a stack allocated string ([char], but this is more common in C than Rust) is the simplest introduction to manually managed memory.
The complications have nothing to do with Rust but with how computers manage and allocate memory. You might as well also skip C, C++, Zig, and every other language which gives you fine-tuned access to the stack and heap, because you'll run into the same concept.
Nit: A &str doesn't mean it has to be static, a &'static str does (which are a subset of &str). A &str can easily point to a dynamic String's heap storage too.
Every now and then I worry about the rust ecosystem growing too fast and there being too many JavaScript expats flooding cargo with useless code and poorly thought out abstractions, etc…
Thank you for reminding me that most people don’t have the patience to even learn something that makes them think even the tiniest bit. Most of the JavaScript people won’t even get past a hello world program. I think we’re mostly safe.
> Thank you for reminding me that most people don’t have the patience to even learn something that makes them think even the tiniest bit.
You think Rust makes you think "the tiniest bit"?
I mean, sure, there's a lot of excess cognitive burden so that you think more about the language features than about your program logic, but you surely aren't claiming that that is a good thing.
The concept of a string’s primary storage being separate from a view/pointer to its contents, is a necessary distinction to draw if you want to minimize copies and still not require garbage collection. Any language that gives you control over your memory is going to need this distinction. Thinking of ownership is a necessary cognitive burden if you want to avoid unnecessary allocations or garbage collection pauses.
People coming from JS or Python look at these burdens and think “rust sucks because I have to worry about string allocation”, without caring that all languages have to deal with this somehow, and if they’re not making the programmer think of it, the language will have to manage itself in a way that won’t always be optimal.
The analogy I like to make is that if a JS developer thinks it’s bad for a language to make them deal with some abstraction, the should consider that their JavaScript VM itself needs to be written in a language that makes someone care about this stuff. JS only works at all because some engineer writing C++ is thinking deeply about string lifetimes. It can’t just be JavaScript all the way down.
However if you’re using Rust in an environment that could just as easily use a GC’d language, you could definitely make the case that it’s the wrong tool for the job. Not everything needs to be written in a low level language, but for the stuff that does, I’m glad Rust exists.
And I’m also glad that people who don’t understand the tradeoffs are “skipping” rust entirely. It’s not gatekeeping, it’s telling people who don’t want to be here that it’s ok: you don’t need to code in rust, maybe it’s just not for you.
The reason for that is simple though: &String converts to &str, but not the other way around... so you should always use &str so that your code works with either, and notice that literal strings are &str.
I think Rust has lots of warts, but I don't see this as one of them (at least it's something you get irritated at only once, but then never have problems with).
I’m barely familiar with rust and forgot about this aspect, if I ever knew it.
Seems pretty sensible though. String is dynamic data on the heap that you own and can modify. str is some data somewhere that you can’t modify.
C has this distinction as well. Of course, in typical C fashion, the distinction isn’t expressed in the type system in any way. Instead, you just have to know that this char* is something you own and can modify and that char* just a reference to some data.
Higher level languages typically unify these ideas and handle the details for you, but that’s not rust’s niche.
>String is dynamic data on the heap that you own and can modify. str is some data somewhere that you can’t modify.
This is not the definition. You can modify both. Being able to modify something depends on whether you can do something with a &mut reference to it, and both &mut String and &mut str provide methods for modifying them.
The difference between the two types is just that String owns its allocation while str doesn't. So modifying a String is allowed to change its bytes as well as add and remove bytes, the latter because the String owns its allocation. Modifying a str only allows changing its bytes.
&str is basically a C string allocated on the stack while String is like a Java string, an object on the heap with a reference to a raw string hidden from plain sight. To avoid unnecessary and unintended allocations and other expensive memory operations, operating on &str is usually preferred for performance reasons.
String almost transparently casts down to &str so in practice you rarely care about the difference when calling library code.
If you're coming from a language that doesn't have a distinction between character arrays and string objects, you're probably fine just using &str.
If you're coming from a higher level language like JS or Python, you're probably used to paying the performance price for heap allocation anyway so you might as well use String in Rust as well and only start caring when performance is affected.