&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.
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 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.
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.
Still, I find Scala and Haskell community more elegant and intellectually superior when it comes to gatekeeping.
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.
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.
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.
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.