Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Vec::last_or_push and friends #465

Open
balt-dev opened this issue Oct 13, 2024 · 14 comments
Open

Add Vec::last_or_push and friends #465

balt-dev opened this issue Oct 13, 2024 · 14 comments
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api

Comments

@balt-dev
Copy link

balt-dev commented Oct 13, 2024

Proposal

Problem statement

Getting a reference to the value you just pushed to a vector requires an extra call and unwrap:

let mut v = Vec::new();
v.push(42);
*v.last_mut().unwrap() = 43;

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 as

if x.is_empty() {
    x.push(0);
}
let last_mut = x.last_mut().unwrap();
*last_mut += 1;

With this, this could be the following:

*x.last_mut_or_push(0) += 1;

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.

@balt-dev balt-dev added api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api labels Oct 13, 2024
@balt-dev
Copy link
Author

balt-dev commented Oct 13, 2024

I'm willing to make a PR with this if everyone's okay with these changes as-is.

@balt-dev
Copy link
Author

Note: It might be a good idea to have first_or_push and friends as well.

@programmerjake
Copy link
Member

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?

@balt-dev
Copy link
Author

balt-dev commented Oct 13, 2024

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.

@balt-dev
Copy link
Author

balt-dev commented Oct 20, 2024

Any discussion on this? Like I said, if we're fine with it then I can make a pull request and such.

@pitaj
Copy link

pitaj commented Oct 20, 2024

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.

@balt-dev
Copy link
Author

Ah, they do? Is it publicly available?
Also, yes, the push_mut method seems like a nice compromise.

@cuviper
Copy link
Member

cuviper commented Oct 21, 2024

Ah, they do? Is it publicly available?

It's all the issues like yours with the ACP label: api-change-proposal A proposal to add or alter unstable APIs in the standard libraries

@joshtriplett
Copy link
Member

👍 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().

@joshtriplett
Copy link
Member

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.

@joshtriplett
Copy link
Member

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.

@cuviper
Copy link
Member

cuviper commented Nov 12, 2024

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,
}

This is OK in local use, but it falls afoul of borrowck limitations if you need to return that lifetime: playground

@scottmcm
Copy link
Member

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.)

@pitaj
Copy link

pitaj commented Nov 12, 2024

It already works with polonius

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api
Projects
None yet
Development

No branches or pull requests

6 participants