7 способов получения значения из Vec<_> в Rust

На протяжении нескольких дней мне всё время не давал покоя вопрос: каким способом лучше всего вывести данные n-того элемента? Допустим у нас есть вектор некоторых значений (u32 или i32) и мы хотим вывести на печать второй элемент.

fn main() {
    let a = vec![1, 2, 3, 42, 7];
    let n = 2;
    println!("a[{}] = {}", n, /* insert here */);
}

Реализации

Очевидный вариантом является явное обращение по индексу к элементу

println!("a[{}] = {}", n, a[n]);

Но сколько есть способов получить элемент из списка и в чём различия между ними?

Хорошим тоном было бы использование функции get. В случае если индекс выходит за пределы нашего вектора мы могли бы обработать данную ситуацию и сообщить об ошибке.

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

println!("a[{}] = {}", n, a.get(n).unwrap());

Ещё одним способом получения n-того элемента из вектора является использование итератора и метода nth

println!("a[{}] = {}", n, a.iter().nth(n).unwrap());

как и композиция методов skip и next

println!("a[{}] = {}", n, a.iter().skip(n).next().unwrap());

Так же можно обратится к трейту Index и использовать его для доступа к значению

use std::ops::Index;
// ...
println!("a[{}] = {}", n, a.index(n));

Ну и под конец самое интересное — использование unsafe методов.

Тут у нас не очень большой выбор: использовать метод get_unchecked или прямое обращение к указателю, ну или почти прямое :).

Использование get_unchecked выглядит следующим образом

unsafe {
    println!("a[{}] = {}", n, a.get_unchecked(n));
}

И обращение к указателю, где нужно использовать разыменование

unsafe {
    println!("a[{}] = {}", n, *(a.as_ptr().offset(n)));
}

Сравнение и результаты

Закончив с кодом перейдём к сравнению данных методов. Для этого используем несколько видов компиляции и будем сравнивать по размеру выходного файла

Метод\Оптимизацияdefaultreleaserelease+ltorelease+dynamic
[n]581 552572 768543 68810 184
get(n)+248+72+80+72
iter().nth(n)+5216+72+80+72
iter().skip(n).next()+5456+72+80+80
index(n)+80+72+80+80
get_unchecked(n)+168+72+80+72
as_ptr().offset(n)+56000

Лучшие результаты показали два метода (если не считать компиляцию без оптимизации) — [n] и as_ptr().offset(n), оно и очевидно в следствии прямого доступ к переменной.

Метод основанный на index(n) показал стабильный результат в не зависимости от оптимизации кода. Остальные имеют стабильный размер при оптимизации, что приводит к мысли — они все преобразуются к одному виду.

Делаю из всего этого вывод: используйте соответствующим метод под конкретную задачу, но всё-таки прямое обращение по индексу наиболее экономное :)

Полезные ссылки

[1] The Rust Standard Library

[2] The Rust Programming Language: eng, rus

[3] Rust Playground

Назад