我正在为MacOS编写一些使用metal机箱的GPU代码.为此,我通过调用以下命令来分配Buffer对象:

let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared)

这是苹果的金属API的FFIS,它分配一个CPU和GPU都可以访问的内存区域,并且Rust包装器返回一个Buffer对象.然后,我可以通过执行以下操作来获得指向该内存区域的指针:

let data = buffer.contents() as *mut u32

在口语意义上,这一记忆区域是未初始化的.然而,在铁 rust 的意义上,这块内存区域是"未初始化的"吗?

Is this sound?

let num_bytes = num_u32 * std::mem::size_of::<u32>();
let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared);
let data = buffer.contents() as *mut u32;

let as_slice = unsafe { slice::from_raw_parts_mut(data, num_u32) };

for i in as_slice {
  *i = 42u32;
}

在这里,我将u32写入FFI返回给我的一个内存区.从nomicon人开始:

...通常,当我们使用=为Rust类型判断器认为已经初始化的值(如x[i])赋值时,存储在左侧的旧值将被丢弃.这将是一场灾难.然而,在本例中,左侧的类型是MaybeUninit&lt;Box&gt;,而丢弃它不会做任何事情!有关此丢弃问题的更多讨论,请参见下面的内容.

from_raw_parts条规则中没有一条被违反,并且u32没有Drop方法.

  • 尽管如此,这是声音吗?
  • 在向其写入之前,从该区域(以u32为单位)读取数据是否合理(除了无稽之谈)?存储器区域是有效的,并且为所有位模式定义了u32.

Best practices

现在考虑一个确实有Drop方法的类型T(并且您已经做了所有bindgen#[repr(C)]的胡说八道,以便它可以跨越FFI边界).

在这种情况下,我们应该:

  • 通过用指针扫描区域并调用.write()来初始化Rust中的缓冲区?
let as_slice = unsafe { slice::from_raw_parts_mut(data as *mut MaybeUninit<T>, num_t) };

for i in as_slice {
  *i = unsafe { MaybeUninit::new(T::new()).assume_init() };
}

此外,在初始化区域之后,Rust编译器如何记住该区域在程序后面对.contents()的后续调用中被初始化?

Thought experiment

在某些情况下,缓冲区是GPU内核的输出,我想要读取结果.所有的写入都发生在Rust控制之外的代码中,当我调用.contents()时,内存区域的指针包含正确的uint32_t个值.这个思维实验应该传达我对这一点的担忧.

假设我调用C的malloc,它返回未初始化数据的已分配缓冲区.是否从该缓冲区读取u32值(指针正确对齐并在范围内),因为任何类型都应该直接进入未定义的行为.

然而,假设我调用calloc,它在返回缓冲区之前将其置零.如果您不喜欢calloc,那么假设我有一个FFI函数,它调用Malloc,用C显式地写出0,uint32_t类型,然后将这个缓冲区返回给Rust.该缓冲器is用有效的u32位模式初始化.

  • 从Rust的Angular 来看,malloc返回"未初始化"数据,而calloc返回初始化数据吗?
  • 如果情况不同,Rust编译器如何知道两者在可靠性方面的差异?

推荐答案

当您有一个内存区域时,需要考虑多个参数:

  • 其规模是最明显的.
  • 它的一致性仍然有些明显.
  • 不管它是否被初始化--值得注意的是,对于像bool这样的类型,它是否被有效值初始化,因为并不是所有位模式都是有效的.
  • 是否同时读取/写入.

着眼于更棘手的方面,建议是:

  • 如果内存可能未初始化,请使用MaybeUninit.
  • 如果内存可能是并发读/写的,请使用同步方法--可以是MutexAtomicXXX或...

就是这样.这样做永远是合理的,不需要寻找"借口"或"例外".

因此,在您的 case 中:

let num_bytes = num_u32 * std::mem::size_of::<u32>();
assert!(num_bytes <= isize::MAX as usize);

let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared);

let data = buffer.contents() as *mut MaybeUninit<u32>;

//  Safety:
//  - `data` is valid for reads and writes.
//  - `data` points to `num_u32` elements.
//  - Access to `data` is exclusive for the duration.
//  - `num_u32 * size_of::<u32>() <= isize::MAX`.
let as_slice = unsafe { slice::from_raw_parts_mut(data, num_u32) };

for i in as_slice {
    i.write(42);  //  Yes you can write `*i = MaybeUninit::new(42);` too,
                  //  but why would you?
}

// OR with nightly:

as_slice.write_slice(some_slice_of_u32s);

Rust相关问答推荐

为什么父作用域中的变量超出了子作用域

为什么单元类型(空元组)实现了`Extend`trait?

在HashMap中插入Vacant条目的可变借位问题

展开枚举变量并返回所属值或引用

为什么我可以跟踪以前borrow 过的变量?房主在哪里?

如何将像烫手山芋一样不透明的值从一个Enum构造函数移动到下一个构造函数?

用 rust 蚀中的future 展望 struct 的future

在Rust中判断编译时是否无法访问

当发送方分配给静态时,Tokio MPSC关闭通道

Rust将String上的迭代器转换为&;[&;str]

我可以解构self 参数吗?

Nom 解析器无法消耗无效输入

go 重并堆积MPSC通道消息

为什么这段 Rust 代码会在没有递归或循环的情况下导致堆栈溢出?

Rust Serde 为 Option:: 创建反序列化器

如何获取函数中borrow 的切片的第一部分?

为什么在 rust 中删除 vec 之前应该删除元素

覆盖类型的要求到底是什么?为什么单个元素元组满足它?

返回引用的返回函数

有什么办法可以用 Rust 访问 Windows 最近的文件夹吗?