story
As others have mentioned, various kinds of string types are baked into the language which makes it ergonomic to do “the right thing” from the get go, but hard to say. I would be skeptical of claims that it would make a difference, especially in the interim where you now have an added impedance mismatch with C++, Rust, C.
The expression of what C++ does here in Rust is awkward and, I think, nobody has proposed it because you'd never write that. Basically C++ char* is a raw pointer. Rust does have those, but you'd never use them in this context.
What you would use is either the borrowed slice reference &str or the owning String type, but in both cases we have an owned object and there's our crucial difference. If you've got the owned String, and I needed an owned String, I should ask for your owned String, and we're done.
In C++ "dropping" ownership as you describe is no big deal, the C++ design doesn't care, but in Rust if you actually drop(foo) it's gone. The references to it can't out-live that, if it's gone then they're gone. If you write code that gives away references and then tries to drop the thing they're references to, Rust will object that this is nonsense, because it is nonsense, you need to ensure those references are gone before dropping the thing they refer to.
As a result I feel you're greatly under-estimating the ergonomic difference.
I think you’ve built a straw man of my argument and then argued with that.
Clearly I meant that it seems possible that a sufficient complicated call stack could still be set up to jump between needing the owned String type and the borrowed &str type. That’s what I meant by dropping ownership as that’s what’s happening in the c++ code when you go between char*/string (the API is dropping its need for ownership). The argument of “ If you've got the owned String, and I needed an owned String, I should ask for your owned String, and we're done” is weak because that same argument would apply to C++ code and yet the code still ended up that way when you pasted together components in a very large code base. Now maybe it’s a bit simpler because you have string, string&, const string&, and const char* and doing that antipattern that happened in C++ just wouldn’t be ergonomic in Rust. Maybe. But that feels like a very thin argument and not “this is impossible in Rust”.
The "I should ask for your owned String" argument does not apply equally well in C++ because of a crucial design infelicity in C++. Your caller may well not have an owned std::string.
In C++ raw char pointers are totally a thing. Because std::string is a late addition (if you learned C++ in the early 1990s a "string" class was maybe an interesting exercise, not a library type) the string literals aren't a built-in string type, and much of the API isn't shaped for such a type either.
Now, the effect is those are (sometimes) owning pointers, it is possible I own some C++ string in this sense, and all I have is a pointer into it. If I give you that pointer, it's not because I didn't give you the owned string, that pointer is my owned string. You want a std::string and there's no reason I would have one at all.
You can mutate these strings, but of course you can't extend them because you've got no way to know how to communicate with the allocator, maybe they live on the stack, or in a private heap. At the time this seemed like a good idea, today we don't think so.
// some process managed by team a
fn caller1(...) {
String s;
level1Callee(&s);
}
// some process managed by team b
fn caller2(s: &str) {
level1Callee(s);
}
// library 1 by team c
fn level1Callee(iDontNeedOwnershipOrDoI: &str) {
level2Callee(iDontNeedOwnershipOrDoI.to_owned())
}
// library 1 by team d
fn level2Callee(iNeedStrongOwnershipBecauseIMutateTheStringAnyway: String) {
level3Callee(&iNeedStrongOwnershipBecauseIMutateTheStringAnyway)
}
This is roughly what happened in Chrome as I understand it (except multiple times because of independent libraries that didn't notice that they probably should have just made a copy to begin with). Let's pretend the codebase had been written originally in Rust. How does Rust avoid this problem from coming up? This didn't happen in Chrome because of ownership. It came up organically because of years of refactoring obfuscated things. For example, level2Callee started out not needing strong ownership but then started calling a library that did (refactoring a complex codebase is very hard & time consuming). Rinse & repeat after many years. Now maybe Rust tooling is better able to point out the unnecessary acquiring/dropping of the strings but that seems unlikely - the problem is statically very difficult to lint around.