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)));
}
Сравнение и результаты
Закончив с кодом перейдём к сравнению данных методов. Для этого используем несколько видов компиляции и будем сравнивать по размеру выходного файла
- default:
$ rustc
- release:
$ rustc -O -C debuginfo=0
- release+lto:
$ rustc -O -C debuginfo=0 -C lto
- release+dynamic:
$ rustc -O -C debuginfo=0 -C prefer-dynamic
Метод\Оптимизация | default | release | release+lto | release+dynamic |
---|---|---|---|---|
[n] | 581 552 | 572 768 | 543 688 | 10 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) | +56 | 0 | 0 | 0 |
Лучшие результаты показали два метода (если не считать компиляцию без оптимизации) — [n] и as_ptr().offset(n), оно и очевидно в следствии прямого доступ к переменной.
Метод основанный на index(n) показал стабильный результат в не зависимости от оптимизации кода. Остальные имеют стабильный размер при оптимизации, что приводит к мысли — они все преобразуются к одному виду.
Делаю из всего этого вывод: используйте соответствующим метод под конкретную задачу, но всё-таки прямое обращение по индексу наиболее экономное :)
Полезные ссылки
[2] The Rust Programming Language: eng, rus
[3] Rust Playground