如何将 C 变长数组代码转换为 Rust?

Posted

技术标签:

【中文标题】如何将 C 变长数组代码转换为 Rust?【英文标题】:How to convert C variable-length array code to Rust? 【发布时间】:2015-08-06 01:48:36 【问题描述】:

我知道 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 尝试 (example in 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);

但是被拒绝了:

[&amp;mut [f32]] 类型的集合不能从 &amp;mut [f32] 类型元素的迭代器构建

很遗憾,这听起来和我想要做的完全一样!

我尝试过使用固定大小的数组,但 Rust 不支持它们的泛型,所以我不能从迭代器中填充它,也不能在类似 C 的循环中填充它们,因为循环中的引用不会超过它。

特征 core::iter::FromIterator&lt;&amp;mut [f32]&gt; 没有为 [&amp;mut [f32]; 4] 类型实现


另一种从固定大小数组中获取内存片的方法也失败了:

let mut row_tmp: [&mut [f32]; 4] = unsafemem::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 作为可变对象

【问题讨论】:

【参考方案1】:

arrayvec 是一个库,可以满足您的需求。 (另外,你可能想要iter_mutas_mut 而不是iteras_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 中的大部分复杂性在于处理为可变数量的项目运行的析构函数。但是由于&amp;mut _ 没有析构函数,你也可以只使用固定大小的数组。但是您必须使用unsafe 代码并注意不要读取未初始化的项目。 (固定大小的数组不实现FromIterator,这是Iterator::collect 使用的。)

(Playpen)

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 []];

较短的[&amp;mut []; 4] 初始化语法不适用于此处,因为&amp;mut [_] 不可隐式复制。类型注释是必要的,所以你不会得到[&amp;mut [_; 0]; 4]

【讨论】:

这太棒了西蒙,为创造它而激动!

以上是关于如何将 C 变长数组代码转换为 Rust?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用基数排序对变长字符串数组进行排序?

GCC 如何实现变长数组?

快学Scala 第三课 (定长数组,变长数组, 数组循环, 数组转换, 数组常用操作)

变长数组 - 转

c中可以定义变长数组吗

关于在C语言 声明带有一个变长数组参数的函数