Каков тип возврата операции индексирования?

Я пытаюсь, безуспешно, поиграть с кусочками.

Я сократил свой первый выпуск до:

fn at<'a, T>(slice: &'a [T], index: usize) -> &'a T {
    let item = slice[index];
    item
}

Я ожидаю, что возвращаемый тип slice[index] будет ссылкой, учитывая документацию:

pub trait Index<Index> {
    type Output;
    fn index(&'a self, index: &Index) -> &'a <Self as Index<Index>>::Output;
//                                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}

Однако компилятор дает мне ошибку:

error[E0308]: mismatched types
 --> src/main.rs:3:5
  |
3 |     item
  |     ^^^^ expected reference, found type parameter
  |
  = note: expected type '&'a T'
             found type 'T'

Который я интерпретирую как означающий, что тип item не соответствует возвращаемому типу функции (я ввел item исключительно для цели отладки, чтобы разделить оценку выражения от возврата).

Если я переключу тип возврата на T, который является типом item, я получаю другое сообщение об ошибке:

error[E0508]: cannot move out of type '[T]', a non-copy slice
 --> src/main.rs:2:16
  |
2 |     let item = slice[index];
  |                ^^^^^^^^^^^^
  |                |
  |                cannot move out of here
  |                help: consider using a reference instead: '&slice[index]'

Немного поправившись, я обнаружил два обхода:

fn at<'a, T>(slice: &'a [T], index: usize) -> &'a T {
    &slice[index]
//  ^
}

fn at<'a, T>(slice: &'a [T], index: usize) -> &'a T {
    let ref item = slice[index];
//      ^~~
    item
}

заставляя тип быть ссылкой, делает трюк.

Почему эти махинации необходимы в первую очередь? Я делаю что-то неправильно?

Ответ 1

Это немного полезная эргономика, которую компилятор делает для вас, чтобы код выглядел немного лучше.

Возвращаемое значение признака Index является ссылкой, но компилятор автоматически вставляет разыменование для вас, когда вы используете синтаксис sugared []. Большинство других языков просто вернут элемент из массива (копируя его или возвращая другую ссылку на объект, независимо от того, что подходит).

Из-за важности семантики move/copy Rust вы не всегда можете сделать копию величиной, поэтому в этих случаях вы обычно будете использовать &:

let items = &[1u8, 2, 3, 4];

let a: u8 = items[0];
let a: u8 = *items.index(&0); // Equivalent of above

let b: &u8 = &items[0];
let b: &u8 = &*items.index(&0); // Equivalent of above

Обратите внимание, что значение индексации также автоматически принимается по ссылке, аналогично автоматическому разыменованию.

Ответ 2

Нет, вы все делаете правильно. Хотя метод index() возвращает ссылку, когда он вызывается в операции индексирования, его результат разыгрывается автоматически. Это делается для того, чтобы индексирование было более естественным: на всех языках, где существует какой-то оператор индексирования (в основном C и C++), он сам возвращает значения, а не ссылки в контейнеры.

Чтобы получить ссылку в сборнике, вам нужно либо применить ссылочный оператор явно (например, в первом "обходном пути"), либо использовать ссылочный шаблон (как во втором).