Passing vector by reference vs passing a slice to a function

If v is a vector consisting of u32 and we pass &v to a function such that inside the function x=&v. Then whenever x[0] is used does rust first check where v is stored and after that check where v[0] is stored? So checking is done twice

If we instead pass x=&v[..] then would rust only need to check where v[0] is stored?

Does checking twice come at a significant performance overhead if it is done repeatedly.

Should we always pass &v[..] in a function instead of &v?

It depends on compiler optimizations. In most cases, you can expect the immediate double indirection to be optimized away. (If, for example, you collect references to vectors in a collection, then of course it can't be optimized out.)

In general, accepting &Vec<_> explicitly is problematic for a different reason – it only works when you literally have a Vec, it doesn't work with other types that coerce to a slice (eg. arrays or VecDeque).

1 Like

First, let's address the overall question:

Mostly. In particular, given the following functions:

fn a(x: &Vec<u32>) { ... }
fn b(x: &[u32]) { ... }

You should prefer b over a. a requires that you pass it a Vec, whereas b can be used with many more types that are able to coerce to or produce a &[u32].

That said, there are rare occasions where you might want to use a, but that would only be because you need to access some information that b doesn't provide. The only one I can think of is if you care about the capacity of the underlying Vec. If you don't care about the capacity, I wouldn't bother with a.

There's no "check" going on. What does happen are pointer dereferences. Using &Vec<_> instead of &[_] does involve an extra pointer indirection. This can be slower, but how much slower depends on the exact code you're writing, the machine you're running it on, and how well the compiler does at optimising your code.

I wouldn't be worried about the overhead, unless you have done performance profiling that indicates it is a problem.

However, there's generally no reason to use &Vec<_> over &[_] anyway, so it's a bit of a moot point.

One more point to make:

It's worth noting that the following works:

fn a() {
    let v: Vec<u32> = vec![1, 2, 3];
    b(&v);
}

fn b(x: &[u32]) {
    println!("x[0]: {}", x[0]);
}

Note that you don't have to explicitly say &v[..]. The compiler knows it can convert a &Vec<T> into a &[T] automatically[1], so it does just that.


  1. Specifically, it knows this because of Vec<T>: Deref<Target=[T]>. ↩ī¸Ž

2 Likes

What does "pointer dereferences" mean? How is it different from checking?

fn a(x: &Vec<u32>) {let a=x[0]; }

What is the program actually doing here?
I know that the actual u32 value is copied to another memory location when assigning to a.

I'm not sure what exactly you mean by "checking", but a pointer dereference means following the address represented by the pointer in order to obtain the place that it is pointing to. It's the inverse of taking the address of a place.

If a reference or pointer has type &(mut) T or *[const|mut] T, respectively, then you get a place of type T by dereferencing them.

Let's say you have this code:

fn f(x: &u32) -> u32 {
    let y: u32 = *x;
    y
}

x is a reference. At the machine level, this means it is a pointer to where a u32 is being stored in memory. A pointer is (usually) just a number that refers to a location in memory. You can kind of think of memory as a single, giant u8 array, and a pointer is a location in this array.

The act of taking the pointer and reading the value stored there is a "pointer dereference". You take the reference and de-reference is, yielding a value.

So, when accessing the individual u32, there are two dereferences for &Vec<u32>, and one for &[u32].

"Check" implies some sort of analysis being done to verify something. There is no such work being done when you go from, say, &u32 to u32.

What I realised, however, is you might be talking about this: x[0]. In particular, indexing into a slice actually involves both a pointer dereference and a "check". Specifically, a bounds check (to make sure that 0 is a valid index within the slice).

If that was what you meant, then I was confused by you asking if there were two "checks" involved in indexing a &Vec<u32>. The answer is: no, the bounds check is only done once, but there are two pointer dereferences. Hence, I assumed you were using incorrect terminology.

Does that clarify things?

3 Likes

I meant pointer deference by check. I did not think of other connotations the word check can imply. I asked meaning of pointer deference in follow up question to make sure that rust does not mean something else other than obvious definition of pointer deference.

Yes

1 Like

Re dereferences, graphically the difference between &[T] and &Vec<T> is[1]

+---+---+---+---+---+---+---+---+
| Pointer       | Length        | &[T] (or &str)
+---+---+---+---+---+---+---+---+
  |
  V
+---+---+---+---+---+---+---+---+
| D | A | T | A | . | . | . | ......    [T] (or str)
+---+---+---+---+---+---+---+---+   
  ^
  |
+---+---+---+---+---+---+---+---+---+---+---+---+
| Pointer       | Length        | Capacity      | Vec<T> (or String)
+---+---+---+---+---+---+---+---+---+---+---+---+
  ^
  |
+---+---+---+---+
| Pointer       | &Vec<T> (or &String)
+---+---+---+---+

  1. order of fields is not guaranteed ↩ī¸Ž

6 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.