Rustコトハジメ

プログラミング言語Rustに関する情報をお届けします。

Learn a lot from Index trait

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let a = &v[0];
    v.push(6);
}

If you try to compile this code

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> prog.rs:4:5
  |
3 |     let a = &v[0];
  |              - immutable borrow occurs here
4 |     v.push(6);
  |     ^ mutable borrow occurs here
5 | }
  | - immutable borrow ends here

the compiler rejects your code. I will share you some good stuffs from this small program.

(&v)[0] or &(v[0])

The first thing I confirmed is that where the type &i32 came from. Here is the definition of Index trait:

pub trait Index<Idx: ?Sized> {
    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

It takes a reference and returns a reference. With the explicit lifetime it would be fn index(&'a self, index: Idx) -> &'a Self::Output and it looks sane for me because the output comes from the self.

I firstly thought it is (&v)[0] form analogy to IntoIterator but this because I didn't know [i] is actually a syntax sugar.

The answer is actually &(v[0]). Let's do some experiment to resolve this problem.

Write the line as (&v)[0]

The compiler says ok.

Write the line as &(v[0])

Then the compiler yields the same error as the initial code so we know that &v[0] means &(v[0]).

Discussion

In the official document we can find this sentence.

container[index] is actually syntactic sugar for *container.index(index)

v[i] is actually a syntax sugar for*v.index(i). Since the return value of v.index(i) is &Self::Output then v[i] is of type Self::Output. &v[i] is of a type &i32 because it is a reference to v[i].

So what about (&v)[0]? It will be i32.

To summarize:

  • v.index(i) is of type &Self::Output
  • *v.index(i) is of type Self::Output // (&v)[0]
  • &(*v.index(i)) is of type &Self::Outputである // &v[0]

Dereference a reference and you get the owner as rvalue

ではこの中間のSelf::Outputとは一体何なのか。なぜ、(&v)[0]のバージョンはコンパイルが通るのか?「参照をとったものの値を覗いてる」のだから「参照をとっているようなもの」と考えることも出来て、同じく「borrowしてるからmutable borrow出来ない」と怒られても不思議ではないのだが。

So what is the type Self::Output in the second step above?

To tell the conclusion first, you get the owner when you dereference a reference. When writing as (&v)[0] you copy the ownership pointer to a. I want to share a thread on this topic

Why does dereferencing here involve a move? : rust

Is it really getting ownership?

If we use String instead of i32 then the compiler rejects the code.

fn main() {
    let mut v = vec!["a".to_string(), "b".to_string()];
    let a: String = (&v)[0];
    v.push("c".to_string());
}
error[E0507]: cannot move out of indexed content
 --> prog.rs:3:21
  |
3 |     let a: String = (&v)[0];
  |                     ^^^^^^^
  |                     |
  |                     cannot move out of indexed content
  |                     help: consider using a reference instead: `&(&v)[0]`

This means that the item's ownership is moved to a from v.