Возможно ли иметь массивы с выделенным стеклом с размером, определенным во время выполнения в Rust?
Я ищу эквивалент следующего кода C99:
void go(int n) {
int array[n];
// ...
}
Возможно ли иметь массивы с выделенным стеклом с размером, определенным во время выполнения в Rust?
Я ищу эквивалент следующего кода C99:
void go(int n) {
int array[n];
// ...
}
Это невозможно напрямую, так как в языке, поддерживающем его, нет прямого синтаксиса.
При этом, эта особенность C99 является дискуссионным, он имеет определенные преимущества (кэш локальность и обходя malloc
), но он также имеет недостатки (легко раздутие стек, пни ряд оптимизаций, может оказаться статические смещения в динамические смещения,...).
В настоящее время я бы посоветовал вместо этого использовать Vec
. Если у вас проблемы с производительностью, вы можете взглянуть на так называемую "Оптимизацию небольших векторов". Я регулярно наблюдаю в коде, где требуется производительность для следующего шаблона (в C):
SomeType array[64] = {};
SomeType* pointer, *dynamic_pointer;
if (n <= 64) {
pointer = array;
} else {
pointer = dynamic_pointer = malloc(sizeof(SomeType) * n);
}
// ...
if (dynamic_pointer) { free(dynamic_pointer); }
Теперь это то, что Rust поддерживает легко (и лучше, в некотором роде):
enum InlineVector<T> {
Inline(usize, [T; 64]),
Dynamic(Vec<T>),
}
Ниже приведен пример упрощенной реализации.
Важно то, что теперь у вас есть тип, который:
Конечно, он также всегда резервирует достаточно места для 64 элементов в стеке, даже если вы используете только 2; однако взамен нет вызова alloca
, поэтому вы избегаете проблемы с динамическими смещениями к вашим вариантам.
И, вопреки C, вы по-прежнему пользуетесь отслеживанием продолжительности жизни, чтобы случайно не вернуть ссылку на ваш выделенный стек, расположенный вне функции.
Примечание: для полномасштабной реализации потребуются параметры не-типа, чтобы вы могли настроить 64
... но Rust еще не существует.
Я продемонстрирую наиболее "очевидные" методы:
impl<T: Copy + Clone> InlineVector<T> {
fn new(v: T, n: usize) -> InlineVector<T> {
if n <= 64 {
InlineVector::Inline(n, [v; 64])
} else {
InlineVector::Dynamic(
FromIterator::from_iter(std::iter::repeat(v).take(n))
)
}
}
fn len(&self) -> usize {
match self {
&InlineVector::Inline(n, _) => n,
&InlineVector::Dynamic(ref vec) => vec.len(),
}
}
fn as_slice(&self) -> &[T] {
match self {
&InlineVector::Inline(n, ref array) => array.as_slice().slice_to(n),
&InlineVector::Dynamic(ref vec) => vec.as_slice(),
}
}
fn as_mut_slice(&mut self) -> &mut [T] {
match self {
&mut InlineVector::Inline(n, ref mut array) =>
array.as_mut_slice().slice_to_mut(n),
&mut InlineVector::Dynamic(ref mut vec) =>
vec.as_mut_slice(),
}
}
}
Использование:
fn main() {
let mut v = InlineVector::new(1u32, 4);
v.as_mut_slice()[2] = 3;
println!("{}: {}", v.len(), v.as_slice()[2])
}
Отпечатает 4: 3
, как ожидалось.
Для справки требуемые импорт/настройки:
#![allow(unstable)]
use std::iter::FromIterator;
use std::vec::Vec;
Нет.
Выполнение этого в Rust повлечет за собой возможность хранить DST (динамически размерные типы), такие как [i32]
в стеке, которые язык не поддерживает.
Более глубокая причина заключается в том, что LLVM, насколько мне известно, на самом деле не поддерживает это. Мне дано полагать, что вы можете это сделать, но это значительно затрудняет оптимизацию. Таким образом, я не знаю о каких-либо близких планах, чтобы это разрешить.