迭代时无法分配给不可变索引内容

时间:2016-02-29 02:40:58

标签: collections struct rust

我正在为Rust编写一个用于Java应用程序的库,我正在尝试将数据从Java代码发送到Rust代码。这个数据由我在Rust侧构建的名为Chunk的结构组成。我也发送数据来修改这些结构,所以它们需要是可变的。我收到一条错误,指出Chunk中的HashSet是不可变的,不应该是这种情况。

#[derive(Eq, PartialEq, Hash)]
struct Chunk {
    x: i32,
    y: i32,
    z: i32,
    blocks: [[[i32; 16]; 16]; 16],
}

lazy_static! {
    // static mutable list (or at least it should be)
    static ref CHUNKS: Mutex<HashSet<Chunk>> = Mutex::new(HashSet::new());
}

#[no_mangle]
pub extern fn add_chunk(cx: i32, cy: i32, cz: i32, c_blocks: [[[i32; 16]; 16]; 16]) {
    // create Chunk and put it in the global list
    CHUNKS.lock().unwrap().insert(Chunk {x: cx, y: cy, z: cz, blocks: c_blocks});
}

#[no_mangle]
pub extern fn update_block(x: i32, y: i32, z: i32, id: i32) {
    let cx: i32 = x / 16;
    let cy: i32 = y / 16;
    let cz: i32 = z / 16;

    let rx: i32 = if x > 0 { x % 16 } else { 16 + (x % 16) };
    let ry: i32 = if y > 0 { y % 16 } else { 16 + (y % 16) };
    let rz: i32 = if z > 0 { z % 16 } else { 16 + (z % 16) };

    for c in CHUNKS.lock().unwrap().iter() {
        if c.x == cx && c.y == cy && c.z == cz {

            // ERROR: cannot assign to immutable indexed content `c.blocks[..][..][..]`

            c.blocks[rx as usize][ry as usize][rz as usize] = id;
        }
    }
}

我不知道我是否应该使用VecHashSet,我选择了后者,因为它似乎最简单。

1 个答案:

答案 0 :(得分:4)

原始答案不正确 - HashSet没有iter_mut()方法:更改哈希表的元素是不安全的,因为它们的位置由其哈希值决定,因此如果值更改,则其哈希值也会发生变化,但由于它是就地修改的,因此它不会再正确地放在哈希表中,并且可能会丢失。

因此,最自然的方法是使用HashMap<(i32, i32, i32), Chunk>,如@starblue所建议的那样:

lazy_static! {
    static ref CHUNKS: Mutex<HashMap<(i32, i32, i32), Chunk>> = Mutex::new(HashMap::new());
}

#[no_mangle]
pub extern fn add_chunk(cx: i32, cy: i32, cz: i32, c_blocks: [[[i32; 16]; 16]; 16]) {
    CHUNKS.lock().unwrap().insert((cx, cy, cz), Chunk {x: cx, y: cy, z: cz, blocks: c_blocks});
}

#[no_mangle]
pub extern fn update_block(x: i32, y: i32, z: i32, id: i32) {
    let cx: i32 = x / 16;
    let cy: i32 = y / 16;
    let cz: i32 = z / 16;

    let guard = CHUNKS.lock().unwrap();
    if let Some(chunk) = guard.get_mut((cx, cy, cz)) {
        let rx: i32 = if x > 0 { x % 16 } else { 16 + (x % 16) };
        let ry: i32 = if y > 0 { y % 16 } else { 16 + (y % 16) };
        let rz: i32 = if z > 0 { z % 16 } else { 16 + (z % 16) };

        chunk.blocks[rx as usize][ry as usize][rz as usize] = id;
    }
}

此外,使用哈希映射,您无需遍历整个集合即可通过其坐标获取项目。

原始答案如下。

您的代码几乎正确,您只需使用iter_mut()代替iter()

for c in CHUNKS.lock().unwrap().iter_mut()

或者,或者:

for c in &mut *CHUNKS.lock().unwrap()

iter()返回一个产生不可变引用的迭代器,因此您无法通过它修改任何内容。另一方面,iter_mut()返回一个迭代器,产生 mutable 引用 - 正是你需要的。

此外,不是直接调用iter_mut(),而是依靠IntoIterator实现来引用集合更为惯用:例如,&mut HashSet<T>通过调用{IntoIterator来实现iter_mut()集合{1}},因此for x in &mut hash_set相当于for x in hash_set.iter_mut()。此处需要*,因为unwrap()不仅会返回包含的值,还会返回一个MutexGuard,它会反映互斥锁包含的内容。