我的第一个Rust生成的WASM产生了以下错误,我不知道如何进行调试。
wasm-000650c2-23:340 Uncaught RuntimeError: memory access out of bounds
at dlmalloc::dlmalloc::Dlmalloc::free::h36961b6fbcc40c05 (wasm-function[23]:670)
at __rdl_dealloc (wasm-function[367]:8)
at __rust_dealloc (wasm-function[360]:7)
at alloc::alloc::dealloc::h90df92e1f727e726 (wasm-function[146]:100)
at <alloc::alloc::Global as core::alloc::Alloc>::dealloc::h7f22ab187c7f5835 (wasm-function[194]:84)
at <alloc::raw_vec::RawVec<T, A>>::dealloc_buffer::hdce29184552be976 (wasm-function[82]:231)
at <alloc::raw_vec::RawVec<T, A> as core::ops::drop::Drop>::drop::h3910dccc175e44e6 (wasm-function[269]:38)
at core::ptr::real_drop_in_place::hd26be2408c00ce9d (wasm-function[267]:38)
at core::ptr::real_drop_in_place::h6acb013dbd13c114 (wasm-function[241]:50)
at core::ptr::real_drop_in_place::hb270ba635548ab74 (wasm-function[69]:192)
上下文:最新的Chrome,Rust ism-bindgen代码,从TypeScript自定义元素调用,在shadow DOM中的画布上运行。呈现给画布的数据来自HTML5 AudioBuffer。所有生锈变量都是局部范围的。
如果文档中只显示一个实例,则Web组件可以正常工作,但如果我进一步实例,则会像上面那样转储堆栈跟踪。代码运行没有任何其他问题。
我知道Chrome中存在未解决的内存错误 - 这是它们的样子,还是经验丰富的生锈/开发人员告诉我这是不寻常的?
js-sys = "0.3.19"
wasm-bindgen = "0.2.42"
wee_alloc = { version = "0.4.2", optional = true }
[dependencies.web-sys]
version = "0.3.4"
生锈代码很小,只需将AudioBuffer的两个通道渲染到提供的HTMLCanvasElement:
#[wasm_bindgen]
pub fn render(
canvas: web_sys::HtmlCanvasElement,
audio_buffer: &web_sys::AudioBuffer,
stroke_style: &JsValue,
line_width: f64,
step_size: usize,
) {
// ...
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() }; // !
for channel_number in 0..1 {
channel_data[channel_number] = audio_buffer
.get_channel_data(channel_number as u32)
.unwrap();
}
// ...
我已经尝试评论功能,如果代码没有触及画布但是上面做了,我得到了错误。进行以下更改会导致简单的“out of wam memory”错误。音频文件是1,200 k。
let channel_data: [Vec<f32>; 2] = [
audio_buffer.get_channel_data(0).unwrap(),
audio_buffer.get_channel_data(1).unwrap()
];
编辑:后面的out of memory
错误,对于上面的正确代码,真的扔了我,但它实际上是一个Chrome bug。
您的问题是您创建了一块未初始化的内存,并且没有正确初始化它:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in 0..1 {
channel_data[channel_number] = audio_buffer
.get_channel_data(channel_number as u32) // no need for `as u32` here btw
.unwrap();
}
Range
s(a.k.a。a..b
)在Rust中是独家的。这意味着你的循环不会像你想象的那样迭代两次,而是只有一次你有一个未初始化的Vec<f32>
然后在放弃它时会惊慌失措。
(请参阅Matthieu M.'s answer以获得正确的解释)
这里有一些可能性。
0..2
0..=1
let mut channel_data: [Vec<f32>; 2] = Default::default()
这将正确初始化两个Vec
s。有关如何初始化数组的更完整概述,请参阅What is the proper way to initialize a fixed length array?
作为旁注:避免使用unsafe
,特别是如果你是Rust的新手。
这里有两个问题:
0..1
迭代[0]
(它是独家的)。我们一次检查一下。
unsafe
.一般来说,你应该努力避免unsafe
。使用它的原因很少,并且有很多方法不正确地使用它(例如这里)。
问题。
在这种特殊情况下:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
channel_data[channel_number] = /*...*/;
}
有两个问题:
std::mem::uninitialized
;使用它是一个非常糟糕的主意。它的替代品是MaybeUninitialized
。Rust中没有赋值运算符,为了执行赋值,语言将:
丢弃认为它是Vec
的原始记忆是未定义的行为;在这种情况下,可能的效果是读取并释放一些随机指针值。这可能会崩溃,这可能会释放一个无关的指针,导致后一次崩溃或内存损坏,这是不好的。
解决方案。
没有理由在这里使用unsafe
:
Default
的Vec
实现不分配内存。简而言之:
auto create_channel = |channel_number: u32| {
audio_buffer
.get_channel_data(channel_number)
.unwrap()
};
let mut channel_data = [create_channel(0), create_channel(1)];
简单,安全,高效。
如果你坚持两步初始化,那么使用迭代器而不是索引来避免一个一个错误。
在你的情况下:
let mut channel_data = [vec!(), vec!()];
for (channel_number, channel) = channel_data.iter_mut().enumerate() {
*channel = audio_buffer
.get_channel_data(channel_number as u32)
.unwrap();
}
在Iterator
上有许多实用函数,在这种特殊情况下,enumerate
将iter_mut()
(一个&mut Vec<f32>
)返回的项目包装成元组(usize, &mut Vec<32>)
: