7 ms·
There is no extra allocation. No matter how you do it, you will need at least one allocation to hold the result of combined strings.
by hermanradtke 5y ago
There is no extra allocation. No matter how you do it, you will need at least one allocation to hold the result of combined strings.
- Dylan16807 5y agoYes, you need one. Which would mean using String::with_capacity based on both &str. Not making a string from one side, then very likely resizing it to add the other. The generic code+call will often make two allocations.
- Arnavion 5y ago>Making you do an extra allocation is a bad way to do that. And the only way I can think of to avoid that would give up having the function be generic. Sure, if you want to optimize that, then create your "foo" string with the extra capacity instead of doing `"foo".to_string()`, eg `{ let foo = String::with_capacity("foo".len() + "bar".len()); foo.push_str("foo"); foo }`. It has nothing to do with the generic `add` function; it can stay generic as it is just fine. Even a function specialized for adding String and &str could not go back in time and change how much capacity the String was created with. Edit (1h later): It's also worth noting that: - This desire of `Add::add(&str, &str) -> String` only seems to come up with people learning the language and bringing expectations from other languages. I've never seen production code that would've benefitted from it. - As much as the ship has sailed with libstd functions hiding allocations via the global allocator where you may not expect them, the less new cases get added the better. So I'd rather not have `impl Add<&'_ str> for &'_ str { type Output = String; }` added to libstd. - If you really do have two str literals that you want to concatenate, just use `concat!()`. Not only does it work, but also it works at compile-time and without any allocation (it evaluates to a `&'static str`).
- xigoi 5y agoBut why can't the add function do this itself?
- infogulch 5y agoWhat do you propose, that fn add goes back in time taps the shoulder of the allocator as it's allocating what will eventually be the first argument and ask to give it a bit of extra capacity?
- Dylan16807 5y agoThe coder would go back in time to before they added ".to_string()"
- xigoi 5y agoNo, I'm asking that add("foo", "bar") should automatically allocate a string of length 6.
- magnio 5y agoThis is explained in the official docs: to avoid too many allocations when repeatedly concatenating strings. > Implements the + operator for concatenating two strings. > This consumes the String on the left-hand side and re-uses its buffer (growing it if necessary). This is done to avoid allocating a new String and copying the entire contents on every operation, which would lead to O(n^2) running time when building an n-byte string by repeated concatenation.
- Dylan16807 5y agoWhat does that explain? Let's say you did add(add("foo", "bar"), "baz"). The inner add would return a String, and then all subsequent adds would have the exact same optimization you're quoting. There is no downside, performance-wise.
- masklinn 5y agoThat can not typecheck in Rust, unless you use something really odd like `Into<Cow<‘a, A>> where A: Add<B>` as your input types. And in languages where it does, you need either ubiquitously mutable strings (which everyone’s moved from because it generally screws you up) or refcounting.
- Dylan16807 5y ago> It has nothing to do with the generic `add` function; it can stay generic as it is just fine. Even a function specialized for adding String and &str could not go back in time and change how much capacity the String was created with. The only reason we're converting a parameter to a String outside the function is because it has that awkward signature. I was suggesting a specialization for adding &str and &str that requires minimal code at the call site and handles the capacity issue inside the function. But that means we're no longer just applying + to types that have Add, we've given up all that convenience because it's not compatible with performance. This wouldn't have to be a tradeoff if Add worked differently; having to make this decision is a flaw with Rust. And you could still make it explicit that allocation is happening.
- deleted 5y ago[deleted]
- Dylan16807 5y ago> Edit (1h later): It's also worth noting that: > - This desire of `Add::add(&str, &str) -> String` only seems to come up with people learning the language and bringing expectations from other languages. I've never seen production code that would've benefitted from it. It's not a big problem that strA.into_string() + strB tends to allocate twice, and you wouldn't notice it in production, but it's still a waste of cycles caused by Rust's abstractions. Not even the abstractions, really, they left that implementation out as a reminder to the coder. It's a bit of an icky tradeoff.