New String function `from_reserve`

Sometimes it is useful to create a string from a string literal but also want to make sure that there is extra space for future additions. It is currently possible to do the following:

let mut s = String::reserve(56);
s.push_str("foobar");

However I think it would be clearer in most cases to have something like the following:

let s = String::from_reserve("foobar", 50);

These two behave the same (modulo non-exact allocations). But I would argue that the second is clear in its intent. Plus since it is reserving space in addition (since that is how the reserve method works), you don't need to manually calculate the difference (like in the first case).

Unresolved Questions:

  1. What should this function be called?
    • from_reserve
    • from_and_reserve
  2. Should we have an exact counterpoint as well?
  3. Does this need to be an RFC or could I just submit a PR for an unstable associated functions?

For whether this would be a PR or and RFC, I'd say it depends on whether you'd expect it to be a pattern. Is this String-specific somehow? Should it also apply to Vec? What about other containers? The more it's a one-off the more it should just be a PR; the more it's a convention that would imply that crates should offer it on their own types the more is should be a full RFC.

For example, if this exists, does it imply that there should also be .collect_reserve(10)?

(And of course there's always the usual question of whether this needs to be more than just a helper in your code somewhere, since it's trivial to write in terms of existing things.)

2 Likes

So I potential problem I see with a method like .collect_reserve(10) is that the benefit of this API would be to do everything in one allocation. And unless you are dealing with ExactSizeIterator (or something similar) that is not possible in general.

And furthermore Iterator<T> doesn't even have a .collect_into(&mut vec) so that seems like a better API to have before considering a .collect_reserve(...).

Edit: nvm there is Extend.

I feel like a .collect_into(vec) would still be a nice addition, at least to itertools.

I completely agree. It probably could be a blanket impl based on types that impl Extend<T>.

There absolutely should be a collect_into but last I checked a proposal was stalled on whether it should move or &mut its argument or both :confused:

Why would you want the iterator to be taken by &mut instead of mut self?

Not the iterator but the Extend into which to collect. iter.collect_into(Vec::with_capacity(123)) is one use case, collecting several iterators into one Vec is another.

Ah I see. Given that both would be useful I could see both of the following:

impl<T> Vec<T> {
    fn collect_into(mut self, from: impl IntoIterator<Item = T>) -> Self;

    fn extend_into(&mut self, from: impl IntoIterator<Item = T>) -> &mut self;
}

But that is tangential to what I am proposing because in the general case you cannot know ahead of time how many items will be in an iterator.

I don't think those ones are representative, since extend is already &mut on the container and takes an IntoIterator.

The conversation was more about

trait Iterator {
    fn collect_onto<C: impl Extend<Self::Item>>(self, target: &mut C);
    // or
    fn collect_onto<C: impl Extend<Self::Item>>(self, target: C) -> C;
}

The latter allowing (... long chain of iterator methods...).collect_onto(Vec::with_capacity(100)), but not working well for a container not available as owned.

Sadly there's no blanket impl of Extend for &mut impl Extend (and it's a breaking change to add one) so they can't both be done with the same signature.

1 Like

Ah I see, could such a blanket impl be done across an edition?

Editions can't make breaking changes to the standard library. Code from all editions links to the same copy of libstd.

1 Like

Could C be wrapped in AsMut to support both?

The original RFC issue proposed to use BorrowMut to support both, but it introduced inference problems due to the double generic parameters and so that idea was abandoned.

AsMut<T> is not automatically implemented for T, so it can't support both.

2 Likes