Why does `Vec::push` not return a mutable reference to itself?

Can we have this API?

impl<T> Vec<T> {
    pub fn push(&mut self, value: T) -> &mut Self;
}

So we can allow the following pattern:

pub fn foo() -> Vec<char> {
    Vec::new().push('f').push('o').push('o')
}

We could consider supporting method cascading for Rust. With the support, returning &mut Self is unnecessary.

Smalltalk and Dart have native (syntax-level) support for method cascading.

3 Likes

From that wiki link:

Method cascading is much less common than method chaining – it is found only in a handful of object-oriented languages, while chaining is very common.

Also from https://en.wikipedia.org/wiki/Method_cascading#Comparison_with_method_chaining:

Given a method call a.b(), after executing the call, method cascading evaluates this expression to the left object a (with its new value, if mutated), while method chaining evaluates this expression to the right object.

Chaining

The following chain (in C++):

a.b().c();

is equivalent to the simple form:

b = a.b();
b.c();

Cascading

The following cascade (in Dart):

a..b()
 ..c();

is equivalent to the simple form:

a.b();
a.c();

For what it is worth we already have support for chaining. So I feel that adding cascading would be surprising.

I also feel that chaining is the least surprising of the two since the rule is simpler and more consistent.

In the short term, method cascades can be fairly easily replicated via macros: https://crates.io/crates/cascade

1 Like

Note that one can have non-native method cascading in Rust today via the cascade crate.

EDIT: Well done @17cupsofcoffee, you beat me to it by ~1 min :wink:

1 Like

I think we can't as this is breaking change. This would stop compiling:

fn push_something(v: &mut Vec<usize>) {
    v.push(42) // no semicolon here
}

This is unidiomatic code, but still code that should be accepted today. It would complain about type mismatch after that.

8 Likes

I tried to build rustc locally with the proposed API but it caused multiple type interference breakages.

Definitely not worth it.

4 Likes

You can always implement this yourself by defining an extension trait. As a bonus, you can add an impl for both Vec<T> and &mut Vec<T>. This would fix the error in your example (function foo returns a Vec and the expression inside yields a &mut Vec).

How it could look: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dc51b3bb17827c08a5adaeab5593edeb

3 Likes

Personally I'd expect the return value to be, if anything, a reference to the element:

    pub fn push(&mut self, value: T) -> &mut T;

so you could push a base value and then manipulate it in place.

18 Likes

This is also unnecessary because Vec<T> is Extend<T>, so you can just provide an iterator if you want to append multiple elements.

2 Likes

Given that @lzutao tried with the other form and it broke even rustc how about the following signature:

pub fn push_and_modify(&mut self, value: T) -> &mut T;

Note that that code wouldn't compile with the proposal; it would still need to be

let mut v = Vec::new();
v.push('f').push('o').push('o');
v

But really, the right way to support repeated-push is when we have IntoIterator for arrays and thus you can just do .extend(['f', 'o', 'o']). That's less typing and more efficient, since it can reserve better.

(And of course the actual snippit is better with vec!, but I assume that's tangential and you're actually doing this for a vector you already have, not a new one)

8 Likes