关于rust在使用中的困惑:报错 -问答-阿里云开发者社区-阿里云

开发者社区> 问答> 正文

关于rust在使用中的困惑:报错

kun坤 2020-06-06 14:56:01 151

刚刚接触rust,想用起来,英语不行,看这里有人能帮下忙不

比如用rust写一个游戏服务器,一个HashMap里有很多对象,比如怪物等各种角色,游戏在循环更新对象,在更新对象时,有可能会更改其自身的属性,或因为各种事件,对别的对象进行更改(比如攻击)

那问题来了,在update时,已经borrow了自已跟HashMap,更新别的对象时,又要borrow,这个有什么办法处理吗? RefCell试过了不行,动态borrow只是能通过编译而已

伪代码

/ / 主循环

for key,value in objects.iter_mut {

// borrow了objects

value.update ();
}


// 对象更新
fn update(&mut self) {
  // 下面无法再borrow objects容器跟object
  let other_or_self = get_global_objects () ->find (id); // id是动态的
  other_or_self.set_life (1000); // 需要操作
}

Rust 容器
分享到
取消 提交回答
全部回答(1)
  • kun坤
    2020-06-06 14:56:09

    看了下 servo 中 dom 节点的处理方式,构造了个例子

    use std::cell::{Cell, RefCell};
    
    struct Element {
        flag: Cell<i32>,
        id: Cell<i32>,
        children: RefCell<Vec<RefCell<Element>>>,
    }
    
    impl Element {
        fn new() -> Element {
            Element {
                flag: Cell::new(0),
                id: Cell::new(1),
                children: RefCell::new(Vec::new()),
            }
        }
    
        fn add_child(&self, child: Element) {
            self.flag.set(self.flag.get() + 1);
            self.children.borrow_mut().push(RefCell::new(child));
        }
    
        fn before_remove(&self) {
            self.flag.set(0);
        }
    
        fn after_remove(&self) {
            self.id.set(0);
        }
    
        fn remove_children(&self) {
            self.before_remove();
    
            for e in self.children.borrow().iter() {
                e.borrow().remove_children();
            }
    
            self.after_remove();
        }
    }
    
    fn main() {
        let e1 = Element::new();
        let e2 = Element::new();
        let e3 = Element::new();
        let e4 = Element::new();
        let e5 = Element::new();
        let e6 = Element::new();
        let e7 = Element::new();
    
        e6.add_child(e7);
        e5.add_child(e6);
        e4.add_child(e5);
        e3.add_child(e4);
        e2.add_child(e3);
        e1.add_child(e2);
    
        e1.remove_children();
    
        println!("...");
    }



    Element 中所有的字段用 Cell/RefCell 包装后,所有的成员方法都使用 &self 不可变借用来调用

    remove_children 应该是能还原你原始的问题了。所有要嵌套/递归修改自己的地方都只用 borrow() 不可变借用就行。

    而其他的基础类型字段,如 i32, vec 由于不会嵌套修改,可以 borrow_mut() 修改完马上还回来,保证一句话修改完就行。

    children 我给简写了,实际上由于要再被其他人引用,应该在外面再包一层 Rc 的。

    大致搜了下有类似需求的一些项目,处理方式都是类似这样的。


    ######回复 @DogFeet :c++里,这些地方相对较少,而变更对象时,改其它对象是很多的,在有交互的代码里可以说是大量,成本不一样######回复 @templar : 不需要把每个修改都封装到单独的函数里面。至于说所有路径都测试到,C++也一样有这个问题,如果你在 Objects 的迭代中某个 object.update 中修改了 Objects 的布局,一样可能会导致迭代器出问题,这些只是你在 C++ 中习惯了,知道 update 中有些事情其实也是不能做的。######回复 @DogFeet : borrow_mut在运行中检查,实际中很难所有路径都测试到,所以rust还是太累了,暂时还是不用了,不过这个问题也算是有解决方法了,谢谢了######回复 @DogFeet : 那得很注意,把每个修改都封到单独的函数里,行是行,就是太费劲了~~~######回复 @templar : 转换成你那个例子就是 update 用 &self 调用,只需要 borrow 借用,update 中又一次借用自己也是用 borrow,由于 life 字段被 Cell 包起来了,所以可以通过 &self 修改。而多次 borrow 并不会有什么问题。这样就解决你原来的问题了。######

    已经用上rust了!  赞一个,

    ######用Recell+Mutex试下。######

    如果是多个线程要修改你的objects,则用Arc

    如果只有一个线程多个地方要修改objects,则用Rc

    ######回复 @DogFeet : 搞不定~~求个示例代码。。。这个问题就是持有一个μt T的情况下,调用其成员函数,在该成员函数里,又需要修改自已或别的对象,就是也要获得μt T,在游戏服务器里,放技能这类逻辑,都会修改多个对象的属性###### @templar .而且用Rc了一般就不会再传引用了,你什么时候见过到处 &shared_ptr的。。。。。全局变量的问题,Rust是不推荐的,所以你要写传统语言中常见的单例模式会发现很难。###### @templar Rc没有你说的这种要求,都说了就是非线程安全版的shared_ptr######回复 @DogFeet : 看文档,rc是用于不可变量的,要获得μt还是得传入μt RC<T>,一层层往上推,又得全是μt,而且还要求rc是unique的,可能你说的可行吧,但现在暂时没什么兴趣了,写一个全局变量都这么费劲,哪天有精神了再弄吧,多谢回答######回复 @templar : Rc 就是用来解决你多个地方要共享修改一个元素的,等同于 C++ 非线程安全版 shared_ptr######

    引用来自“DogFeet”的评论

    如果是多个线程要修改你的objects,则用Arc

    如果只有一个线程多个地方要修改objects,则用Rc

     #[derive(Debug)]
    pub struct OBJ {
        x : i32,
    }


    fn main() {
        let mut x1 = Rc::new(OBJ { x: 100 } );
        let mut x2 = x1.clone();


        x1.x = 101;
        x2.x = 102;
        println!("{:?}",x1);
        println!("{:?}",x2);
    }
    src\main.rs(105,5): error : cannot assign to immutable field

    src\main.rs(106,5): error : cannot assign to immutable field

    std::rc::Rc

    A reference-counted pointer type over an immutable value.

    要获得&mut T,还是需要

    fn get_mut(rc: &mut Rc<T>) -> Option<&mut T>[−]

    Unstable

    Returns a mutable reference to the contained value if the Rc<T> is unique.

    Returns None if the Rc<T> is not unique.


    ######

    我想可能rust不适合其它语言那种N层嵌套调用,可能得写成比较扁平的类,Github上的开源,也大都如此,比如技能这个,可能就得把技能拉出来,跟OBJ同层,用RefCell只临时borrow_mut,不过这样又得导致很多其它的不便,也是挺恶心的

    ######
    fn main() {
        let x1 = Rc::new(RefCell::new(OBJ { x: 100 } ));
        let x2 = x1.clone();
    
        x1.borrow_mut().x = 101;
        x2.borrow_mut().x = 103;
    
        println!("{:?}",x1);
        println!("{:?}",x2);
    }



    所有使用你那个HashMap的地方拿到的都是一个类似上面的 x1, x2 的对象,不用保存引用,可变引用的哦,也不会传染到上层哦。

    我比较好奇的是你的 get_global_objects () 打算怎么实现,要实现这种类似单例类会很麻烦的。

    ######https://github.com/Kimundi/lazy-static.rs 这个宏可以简化全局变量######Global是有办法的,参照os.Stdout,比较烦一点,不过可以用宏实现######两次borrow_mut会panic的,在N层嵌套里,要想保持单一个borrow几乎不可能的,一个技能出发一个效果,影响N个对象,N个对象上的反应器又会触发其它效果,完全不可控,这个我在最上面说过不行######我说的两次borrow_mut会panic的,是指先持一个mut,调用其内部函数,在函数里,可能需要borrow自已,或其它对象,一层层函数调下去,需要borrow的对象是动态的,不可知的
    你没仔细看我的问题


    impl for OBJ {
    fn update(&mut self) {
    // 这里borrow另一个,可能是自已,或别人
    // 假如那个对象已经被borrow了,就panic了
    }
    }


    fn main() {
        let x1 = Rc::new(RefCell::new(OBJ { x: 100 } ));
        let x2 = x1.clone();
     
        x1.borrow_mut().update()
     
        println!("{:?}",x1);
        println!("{:?}",x2);
    }
    ######不过RefCell可以解决HashMap的引用了,不必都用iter_mut了######

    引用来自“templar”的评论

    我想可能rust不适合其它语言那种N层嵌套调用,可能得写成比较扁平的类,Github上的开源,也大都如此,比如技能这个,可能就得把技能拉出来,跟OBJ同层,用RefCell只临时borrow_mut,不过这样又得导致很多其它的不便,也是挺恶心的

    如果你能做到扁平的,那就可以直接用 &, &mut 来borrow了,用完直接还掉,就没上面的问题了呀。


    但事实上很难做到的,我也是做游戏的。比如常见的触发器需求,有时候触发器关联了2个对象,这个触发器需要引用这2个对象吧,C++可能就直接用到智能指针了(有的解决方案是用ID做句柄,不过也有麻烦的地方,要手动解除ID的关联,除非能保证ID不回环)。


    所有的对象都被 Objects borrow 了,触发器再 borrow 肯定要出事,这种需求很难通过做平来避免的。######打包成事件扔到顶层循环处理,会勉强可以,不过很不自然,会导致别的问题######

    引用来自“templar”的评论

    我说的两次borrow_mut会panic的,是指先持一个mut,调用其内部函数,在函数里,可能需要borrow自已,或其它对象,一层层函数调下去,需要borrow的对象是动态的,不可知的
    你没仔细看我的问题


    impl for OBJ {
    fn update(&mut self) {
    // 这里borrow另一个,可能是自已,或别人
    // 假如那个对象已经被borrow了,就panic了
    }
    }


    fn main() {
        let x1 = Rc::new(RefCell::new(OBJ { x: 100 } ));
        let x2 = x1.clone();
     
        x1.borrow_mut().update()
     
        println!("{:?}",x1);
        println!("{:?}",x2);
    }

    你说的这个我还没考虑到。

    如果是这样,那确实很麻烦,按照多人共享用 Rc 的策略,如果 Objects 中的 entry 也需要多人共享,那把 entry 也用 Rc 包起来?不过这样,entry.update(&mut self) 就得改写成 entry_update(e: Rc<...>) 了,这样也确实很丑。

    或者是模仿 Rc, Arc 这种用 unsafe 包装内部可变性的方式,把 entry 的 update 用 unsafe 包起来,外面拿 immutable borrow 的 entry 也能通过调用其成员函数来做到修改内部状态。就像 Rc 的 clone。不过这种感觉也不是解决方法。


    我再问问其他看看

    ######unsafe也试过了,在unsafe里不能把获得的μt T作为参数调用其它安全函数
    0 0
云原生
使用钉钉扫一扫加入圈子
+ 订阅

云原生时代,是开发者最好的时代

推荐文章
相似问题
推荐课程