Как преобразовать код массива переменной длины C в Rust?

Я знаю, что Rust не поддерживает массивы переменной длины, но это заставляет меня задуматься, чем их заменить, учитывая, что:

  • Я не хочу выделять и освобождать крошечный Vec в цикле
  • Средство проверки заимствований не позволяет мне вывести код за пределы цикла
  • Есть много ограничений на массивы фиксированного размера, поэтому я не могу понять, как их использовать.

Код C, который я конвертирую, обрабатывает изображение, вызывая обратный вызов в каждой строке, передавая небольшой массив указателей:

float *tmp[img->channels]; // Small, up to 4 elements
for(int y = 0; y < height; y++) {
    for(int ch = 0; ch < img->channels; ch++) {
        tmp[ch] = &img->channel[ch]->pixels[width * y];
    }
    callback(tmp, img->channels);
}

Моя попытка Rust (пример в playpen):

for y in 0..height {
    let tmp = &img.channel.iter().map(|channel| {
        &mut channel.pixels.as_ref().unwrap()[width * y .. width * (y+1)]
    }).collect();
    callback(tmp);
}

Но это отклонено:

коллекция типа [&mut [f32]] не может быть построена из итератора по элементам типа &mut [f32]

К сожалению, это похоже на то, что я пытался сделать!

Я пробовал использовать массив фиксированного размера, но Rust не поддерживает для них дженерики, поэтому я не могу заполнить его с помощью итератора, и я не могу заполнить их в цикле, подобном C, потому что ссылки, взятые в петля не переживает этого.

признак core::iter::FromIterator<&mut [f32]> не реализован для типа [&mut [f32]; 4]


Другой подход с извлечением фрагмента памяти из массива фиксированного размера также не работает:

let mut row_tmp: [&mut [f32]; 4] = unsafe{mem::zeroed()};
for y in 0..height {
    row_tmp[0..channels].iter_mut().zip(img.chan.iter_mut()).map(|(t, chan)| {
        *t = &mut chan.img.as_ref().unwrap()[(width * y) as usize .. (width * (y+1)) as usize]
    });
    cb(&row_tmp[0..channels], y, width, image_data);
}

ошибка: нельзя заимствовать img.chan как изменяемый более одного раза за раз


person Kornel    schedule 24.05.2015    source источник


Ответы (1)


arrayvec - это библиотека, которая делает то, что вы ищете. (Кроме того, вы, вероятно, захотите iter_mut и as_mut вместо iter и as_ref.)

for y in 0..height {
    let tmp: ArrayVec<[_; 4]> = img.channel.iter_mut().map(|channel| {
        &mut channel.pixels.as_mut().unwrap()[width * y .. width * (y+1)]
    }).collect();
    callback(&tmp);
}

Он выделяет фиксированный объем памяти (здесь 4 элемента) в стеке и ведет себя как Vec, размер которого ограничен (до емкости, указанной во время компиляции), но переменный.

Большая часть сложности в arrayvec состоит в том, чтобы иметь дело с запущенными деструкторами для переменного числа элементов. Но поскольку &mut _ не имеет деструктора, вы также можете использовать массив фиксированного размера. Но вы должны использовать unsafe код и быть осторожными, чтобы не прочитать неинициализированные элементы. (Массивы фиксированного размера не реализуют FromIterator, что используется Iterator::collect.)

(Манеж)

let n_channels = img.channel.len();
for y in 0..height {
    let tmp: [_; 4] = unsafe { mem::uninitialized() }
    for (i, channel) in img.channel.iter_mut().enumerate() {
        tmp[i] = &mut channel.pixels.as_mut().unwrap()[width * y .. width * (y+1)];
    }
    // Careful to only touch initialized items...
    callback(&tmp[..n_channels]);
}

Изменить: небезопасный код можно заменить на:

let mut tmp: [&mut [_]; 4] = [&mut [], &mut [], &mut [], &mut []];

Более короткий синтаксис инициализатора [&mut []; 4] здесь не применяется, поскольку &mut [_] не может быть скопирован неявно. Аннотация типа необходима, чтобы вы не получили [&mut [_; 0]; 4].

person Simon Sapin    schedule 24.05.2015
comment
Это потрясающий Саймон, спасибо за его создание! - person Jorge Israel Peña; 25.05.2015