Open balt-dev opened 1 month ago
I'm willing to make a PR with this if everyone's okay with these changes as-is.
Note: It might be a good idea to have first_or_push
and friends as well.
I think having a Vec::push
equivalent that returns a reference to the just-pushed value is independently useful, maybe name it Vec::push_and_get
?
At that point, why not have Vec::push
just return &mut T
(barring any concerns over the breaking change)?
This is what #464 was about, but I closed it in favor of this because I don't want to focus on two ACPs at once. I might reopen it after this one is settled if it's independently useful like you say, but I'd need a better example for it.
Any discussion on this? Like I said, if we're fine with it then I can make a pull request and such.
The libs team has a backlog of these to get through, be patient.
Personally, I think the push(T) -> &mut T
idea is better. Rather than changing push
, I would suggest adding a new method, push_mut
. That avoids the breaking change issue.
Ah, they do? Is it publicly available?
Also, yes, the push_mut
method seems like a nice compromise.
Ah, they do? Is it publicly available?
It's all the issues like yours with the ACP label: https://github.com/rust-lang/libs-team/labels/api-change-proposal
:+1: for shipping push_mut
returning a &mut T
.
Sadly I don't think the borrow checker will let you get away with v.last_mut().unwrap_or_else(|| v.push_mut(x))
. But I think push_mut
will still be a substantial improvement and eliminate the need for .unwrap()
.
We discussed this in a libs-api meeting (with somewhat limited attendance), and those present were in favor of adding push_mut
.
With push_mut
, it should be possible to write last_mut_or_push
as
match v.last_mut() {
None => v.push_mut(x),
Some(r) => r,
}
last_mut_or_push_with
would be straightforward as well (replace x
with f()
).
So, we're approving push_mut
for now, and we'd like to wait to see if the match patterns arise often enough to warrant helpers.
Speaking for myself: if push
and push_mut
both exist, then I think push_mut
should be #[must_use]
, since there's no reason to call it if not using the return value.
With
push_mut
, it should be possible to writelast_mut_or_push
asmatch v.last_mut() { None => v.push_mut(x), Some(r) => r, }
This is OK in local use, but it falls afoul of borrowck limitations if you need to return that lifetime: playground
That's the well-known case that's already on the list of things we want to change in the future, right? Makes me still want to hold off on adding a specific API for it unless we find out that it's extraordinarily common. (Or we find out that the borrowck changes are even further out than expected.)
Proposal
Problem statement
Getting a reference to the value you just pushed to a vector requires an extra call and unwrap:
Motivating examples or use cases
I'm currently porting https://github.com/Naruyoko/OmegaNum.js/ to Rust, and this is a common pattern all over:
x.array[i+1]=(x.array[i+1]||0)+1;
This is directly translated asWith this, this could be the following:
which is much nicer.
Solution sketch
Add functions for
Vec::last_or_push(T) -> &T
,Vec::last_mut_or_push(T) -> &mut T
,Vec::last_or_push_with(impl FnOnce() -> T) -> &T
,Vec::last_mut_or_push_with(impl FnOnce() -> T) -> &mut T
, which would get the last element of the vector, or push a value and return a reference to that.Alternatives
Alternatively,
Vec::push
could return&mut T
, which would remove the possiblity of a panic - maybe it would be better, but both would be nice. See issue #464.