Подтвердить что ты не робот

Alloca/переменная длина массивов в Rust?

Возможно ли иметь массивы с выделенным стеклом с размером, определенным во время выполнения в Rust?

Я ищу эквивалент следующего кода C99:

void go(int n) {
    int array[n];
    // ...
}
4b9b3361

Ответ 1

Это невозможно напрямую, так как в языке, поддерживающем его, нет прямого синтаксиса.

При этом, эта особенность 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 элементов
  • переходит в кучу в противном случае, чтобы не взорвать стек

Конечно, он также всегда резервирует достаточно места для 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;

Ответ 2

Нет.

Выполнение этого в Rust повлечет за собой возможность хранить DST (динамически размерные типы), такие как [i32] в стеке, которые язык не поддерживает.

Более глубокая причина заключается в том, что LLVM, насколько мне известно, на самом деле не поддерживает это. Мне дано полагать, что вы можете это сделать, но это значительно затрудняет оптимизацию. Таким образом, я не знаю о каких-либо близких планах, чтобы это разрешить.