9

I have immutable vectors a and b where the elements are cheap to copy and I want to create a vector that forms the concatenation of these existing vectors without changing them (*).

An earlier question addressed it if one of the vectors is mutable, so an obvious answer is to first clone vector a, e.g.

let mut r = a.clone();
r.extend(&b);

but that seems neither elegant nor efficient (extending can easily cause one needless reallocation, right?). The (corrected) best alternative I (being a Rust noob) come up with is:

fn cat(a: &Vec<i32>, b: &Vec<i32>) -> Vec<i32> {
    let mut r = Vec::<i32>::with_capacity(a.len() + b.len());
    r.extend(a);
    r.extend(b);
    r
}

Since these elements are cheap to copy, the answer to the more generic question for vectors of strings should apply here, but vec![a, b].concat() only seems to work if you construct the vector of vectors by moving the vectors into it, because vec![&a, &b].concat()yields "no method named concat found".

Is there a one-liner for this seemingly simple job, even if it's not optimal?

---------- (*) turns out there are two meanings to "without changing":

  • Just immutable, which in Rust means that if code compiles, it will not see the variable with a changed value; but the variable's value may be moved out, and future code cannot use the variable anymore.
  • Actually read-only, leaving the variable untouched for further or future code; this is what I am looking for.
Stein
  • 1,558
  • 23
  • 30

3 Answers3

14

concat does work, if used correctly:

fn cat(a: &[i32], b: &[i32]) -> Vec<i32> {
    [a, b].concat()
}

fn main() {
    let a = vec![1, 2, 3];
    let b = vec![7, 8, 9];
    println!("{:?}", cat(&a, &b));
}
hellow
  • 12,430
  • 7
  • 56
  • 79
  • From my outsider point of view, this still mutates `a` and `b`: it moves them, making them unusable further in the code or in further iterations. But I'm beginning to see the term immutable I stuck on the question doesn't mean untouchable in Rust, and doesn't have to. So this looks like the most elegant way to concatenate immutable vectors in my mind. – Stein Nov 26 '18 at 16:42
  • @Stein It doesn't move them. `cat` accepts two slices. You can use `a` and `b` further in `main`. What do you mean by *"untouchable"*? – hellow Nov 26 '18 at 16:55
  • You're right, I can simply [turn your code into an iteration](https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=6ec3e82349560e4eee90702724309f4a). But not if I [try to avoid the intermediate function](https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=6431e07d2c39aa55d6fe937be8d64429). I'll try to understand that later. – Stein Nov 26 '18 at 17:18
  • @Stein Look at the [revisions of my answer](https://stackoverflow.com/posts/53476705/revisions) (#1) and then you may be able to get your "intermediate" function work. Report please :) – hellow Nov 26 '18 at 17:20
  • 1
    Thanks, I got it now. An actual one-liner is `[a.as_slice(), b.as_slice()].concat()` and the function invocation does the slicy thing implicitly. – Stein Nov 26 '18 at 17:25
  • I tried to clartify _untouchable_ (read-only? radical immutability?) versus immutable in the question. Is there an official word it? Something not declared as const but treated as const? – Stein Nov 26 '18 at 17:31
4

Arrays can concat() slices to a vector, you just need to give a stronger hint to the compiler:

let combined = [a.as_slice(), b.as_slice()].concat();

Here's a rust playground to try it out. You can see that neither a nor b are consumed, and combined is a new vector. Note that this fails if you try the & shorthand for borrowing instead of specifying as_slice().

carver
  • 2,229
  • 12
  • 28
3

Editor's note: The OP changed their question after this answer was provided. Refer to the version of the question this answer was created from.

Your first example doesn't really make sense. You mention immutability, but since you are transferring ownership of the vectors to the cat function, it can choose what the mutability is of the variables. In this case, you might as well just reuse the allocation of one of them:

fn cat(mut a: Vec<i32>, b: Vec<i32>) -> Vec<i32> {
    a.extend(b);
    a
}

extending can easily cause needless reallocation

This is technically possible, but extremely unlikely. There's a reason that iterators have the method size_hint — this allows collections to allocate exactly whenever possible.

a one-liner

a.into_iter().chain(b).collect::<Vec<_>>()

This destroys the allocation of the vectors a and b (not of the elements inside them) and creates a new allocation to hold all the items.


If you had immutable slices, you can use the same technique:

fn cat<T: Clone>(a: &[T], b: &[T]) -> Vec<T> {
    a.iter().chain(b).cloned().collect()
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    "extending can easily cause needless reallocation" This is technically possible, but extremely unlikely: I don't mean that during extending it reallocates more than once, just that after the clone it didn't even know about the second part that is going to be appended. – Stein Nov 25 '18 at 16:26
  • My initial example code was a botched attempt to formalize the problem described, so the first part of the answer doesn't apply (some of it belongs with the first linked question instead.) – Stein Nov 25 '18 at 17:26