< 返回版块

jasper2007111 发表于 2024-03-02 16:30

use std::{cell::RefCell, rc::{Rc, Weak}};

struct Parent {
    pub child: Option<Child>,
}

impl Parent {
    pub fn get_value(&self) {
        print!("30");
    }

    pub fn set_child(&mut self) {
        let child = Child {
            parent: Some(self),
        };
        self.child = Some(child);
    }

    pub fn run(&self) {
        println!("run");
        if let Some(c) = &self.child {
            c.borrow().call_parent();
        }
    }
}

struct Child {
    pub parent: Option<Parent>,
}

impl Child {
    pub fn call_parent(&self) {
        self.parent.unwrap().get_value();
    }
}

fn main() {
    println!("Hello, world!");
    let mut p = Parent { child: None };
    p.set_child();
    p.run();
}


以上代码无法运行,尝试改了几次后,都陷入类似无解的困境,这种情况在其他语言中一般很好实现,但是感觉在Rust中困难重重困难,请高手给指点下。

评论区

写评论
ankoGo 2024-03-20 12:52

看来你没领略作者的意思,人家并非只是想要包含的关系,而是,从p->c,或者从c->p都可以访问,也就是父节点可以直接访问到子节点,而子节点也可以直接访问回父节点,看懂没?

--
👇
北海: 这个是典型的面向对象中的模板方法,子类需要调用父类的方法才能完成功能的子类,C++/Java/C#可以轻易做到。

而Rust,可以看下这篇将Rust实现模板方法的文章

《Rust与面相对象二:模板方法》

如果你读懂了这篇文章,子级调用父级的方法,写法结构要变一下,应该是如下设计比较好

struct Parent<C> {
    child: C,
}

struct Child {
    ...
}

/// 重点来了,Parent<Child>才是Parent<_>的子级(对应面相对象里的);而Child,并不是。
impl Parent<Child> {
    fn call_parent(&self) {
        // do sth
        // 不需复杂的Option、内部可变性绕来绕去的
    }
}
ankoGo 2024-03-20 12:05

struct Parent { child: C, }中得C是泛型得意思?为什么不用T呢?很奇怪得命名方式

👇
北海: 这个是典型的面向对象中的模板方法,子类需要调用父类的方法才能完成功能的子类,C++/Java/C#可以轻易做到。

而Rust,可以看下这篇将Rust实现模板方法的文章

《Rust与面相对象二:模板方法》

如果你读懂了这篇文章,子级调用父级的方法,写法结构要变一下,应该是如下设计比较好

struct Parent<C> {
    child: C,
}

struct Child {
    ...
}

/// 重点来了,Parent<Child>才是Parent<_>的子级(对应面相对象里的);而Child,并不是。
impl Parent<Child> {
    fn call_parent(&self) {
        // do sth
        // 不需复杂的Option、内部可变性绕来绕去的
    }
}
北海 2024-03-19 16:51

这个是典型的面向对象中的模板方法,子类需要调用父类的方法才能完成功能的子类,C++/Java/C#可以轻易做到。

而Rust,可以看下这篇将Rust实现模板方法的文章

《Rust与面相对象二:模板方法》

如果你读懂了这篇文章,子级调用父级的方法,写法结构要变一下,应该是如下设计比较好

struct Parent<C> {
    child: C,
}

struct Child {
    ...
}

/// 重点来了,Parent<Child>才是Parent<_>的子级(对应面相对象里的);而Child,并不是。
impl Parent<Child> {
    fn call_parent(&self) {
        // do sth
        // 不需复杂的Option、内部可变性绕来绕去的
    }
}
ankoGo 2024-03-17 11:44

如果是想要线程安全的版本,,那就不应该使用Rc或者RefCell了,而是应该使用Arc和Mutex来保护数据,下面是正确的代码:

use std::sync::{Arc, Mutex};
use std::fmt::Display;

#[derive(Debug)]
struct Parent {
    id: i32,
    child: Option<Arc<Mutex<Child>>>,
}

impl Parent {
    pub fn get_value(&self) {
        println!("{}", self.id);
    }

    pub fn set_child(&mut self, child: Arc<Mutex<Child>>) {
        self.child = Some(child);
    }

    pub fn run(&self) {
        println!("run");
        if let Some(child_ref) = self.child.as_ref() {
            let child = child_ref.lock().unwrap(); // 使用lock来获取Mutex的互斥锁
            child.call_parent(self);
        }
    }
}

#[derive(Debug)]
struct Child {
    parent: Option<Arc<Mutex<Parent>>>,
}

impl Child {
    pub fn call_parent(&self, parent: &Parent) {
        parent.get_value();
    }
}

fn main() {
    println!("Hello, world!");
    let parent = Arc::new(Mutex::new(Parent { id: 123, child: None }));
    let child = Child { parent: Some(Arc::clone(&parent)) };

    let mut p = parent.lock().unwrap(); // 获取Parent的Mutex锁
    p.set_child(Arc::new(Mutex::new(child))); // 设置child

    p.run(); // 运行Parent的run方法

    // 注意:这里打印parent会尝试获取Mutex锁来格式化输出,这可能会导致死锁
    // 如果其他线程正在尝试获取这个Mutex锁的话。
    // 在多线程环境中,通常不建议直接打印锁保护的数据结构。
    println!("{:?}", p);
    // println!("{:?}", child);
}```
ankoGo 2024-03-17 10:41

楼下的才是正解。你这个明显复杂化了

--
👇
ZZG: 在不改变原来框架的情况下好像真的很难,因为

let mut p = Parent { child: None };

导致p是在本地的,任何想让child持有Rc都是不可能的,因为这个parent已经是在栈上了的,只能拿普通的&Parent引用(如果是可变引用&mut Parent就更难了,可能做不了),所以改成

struct Child<'a> {
    pub parent: Option<&'a Parent>,
}

因为Child现在有了一个lifetime参数'a,那么因为Parent结构体中有Child,所以Parent自己肯定也要有一个lifetime参数,所以Parent会长这样

struct Parent<'a> {
    pub child: Option<SomeType<Child<'a>>>,
}

这个SomeType是什么呢,能想到的可能是Box,Rc,Cell,RefCell这些智能指针,或者干脆什么都不要直接Option<Child<'a>>.

然后我们希望调用

p.set_child();

set_child会生成某个Child实例,且将parent的None改成有内容的Some(child),把None改成Some一定需要&mut p,但我们又想构造出一个一直持有parent引用的Child实例,这里会有冲突(能不能解决我也不懂)。

为了避免&mut p和&p不能同时存在的问题,解决方法是上面的SomeType使用RefCell,而且在最开始初始化Parent的时候,不是设置为None,而是RefCell,这样可以用&p来将None改成Some

最后完整代码如下,Parent中新增了一个id主要看child是不是指向了我们原来的那个Parent.

use std::{cell::RefCell, rc::{Rc, Weak}};
use std::cell::Cell;
use std::marker::PhantomData;
use std::mem::MaybeUninit;

struct Parent<'a> {
    id: i32,
    pub child: RefCell<Option<Child<'a>>>,
}

impl<'a> Parent<'a> {
    pub fn get_value(&self) {
        println!("{}", self.id);
    }

    pub fn set_child(&'a self) {
        let child = Child{ parent: Some(self) };
        self.child.replace(Some(child));
    }

    pub fn run(& self) {
        println!("run");

        if let Some( c) = self.child.borrow().as_ref() {
            c.call_parent();
        }
    }
}

struct Child<'a> {
    pub parent: Option<&'a Parent<'a>>,
}

impl<'a> Child<'a> {
    pub fn call_parent(&self) {
        self.parent.unwrap().get_value();
    }
}

fn main() {

    println!("Hello, world!");
    let p = Parent {id: 123, child: RefCell::new(None)};
    p.set_child();
    p.run();
}

最后这个实现是没有memory leak的,但是限制也比较大,就是p在set_child后不能再调用任何&mut 方法

作者 jasper2007111 2024-03-09 22:18

学习了,但这个缺点也挺致命的,感觉Rust越学越感觉水有点深。

--
👇
ZZG: 在不改变原来框架的情况下好像真的很难,因为

let mut p = Parent { child: None };

导致p是在本地的,任何想让child持有Rc都是不可能的,因为这个parent已经是在栈上了的,只能拿普通的&Parent引用(如果是可变引用&mut Parent就更难了,可能做不了),所以改成

struct Child<'a> {
    pub parent: Option<&'a Parent>,
}

因为Child现在有了一个lifetime参数'a,那么因为Parent结构体中有Child,所以Parent自己肯定也要有一个lifetime参数,所以Parent会长这样

struct Parent<'a> {
    pub child: Option<SomeType<Child<'a>>>,
}

这个SomeType是什么呢,能想到的可能是Box,Rc,Cell,RefCell这些智能指针,或者干脆什么都不要直接Option<Child<'a>>.

然后我们希望调用

p.set_child();

set_child会生成某个Child实例,且将parent的None改成有内容的Some(child),把None改成Some一定需要&mut p,但我们又想构造出一个一直持有parent引用的Child实例,这里会有冲突(能不能解决我也不懂)。

为了避免&mut p和&p不能同时存在的问题,解决方法是上面的SomeType使用RefCell,而且在最开始初始化Parent的时候,不是设置为None,而是RefCell,这样可以用&p来将None改成Some

最后完整代码如下,Parent中新增了一个id主要看child是不是指向了我们原来的那个Parent.

use std::{cell::RefCell, rc::{Rc, Weak}};
use std::cell::Cell;
use std::marker::PhantomData;
use std::mem::MaybeUninit;

struct Parent<'a> {
    id: i32,
    pub child: RefCell<Option<Child<'a>>>,
}

impl<'a> Parent<'a> {
    pub fn get_value(&self) {
        println!("{}", self.id);
    }

    pub fn set_child(&'a self) {
        let child = Child{ parent: Some(self) };
        self.child.replace(Some(child));
    }

    pub fn run(& self) {
        println!("run");

        if let Some( c) = self.child.borrow().as_ref() {
            c.call_parent();
        }
    }
}

struct Child<'a> {
    pub parent: Option<&'a Parent<'a>>,
}

impl<'a> Child<'a> {
    pub fn call_parent(&self) {
        self.parent.unwrap().get_value();
    }
}

fn main() {

    println!("Hello, world!");
    let p = Parent {id: 123, child: RefCell::new(None)};
    p.set_child();
    p.run();
}

最后这个实现是没有memory leak的,但是限制也比较大,就是p在set_child后不能再调用任何&mut 方法

ZZG 2024-03-09 10:47

在不改变原来框架的情况下好像真的很难,因为

let mut p = Parent { child: None };

导致p是在本地的,任何想让child持有Rc都是不可能的,因为这个parent已经是在栈上了的,只能拿普通的&Parent引用(如果是可变引用&mut Parent就更难了,可能做不了),所以改成

struct Child<'a> {
    pub parent: Option<&'a Parent>,
}

因为Child现在有了一个lifetime参数'a,那么因为Parent结构体中有Child,所以Parent自己肯定也要有一个lifetime参数,所以Parent会长这样

struct Parent<'a> {
    pub child: Option<SomeType<Child<'a>>>,
}

这个SomeType是什么呢,能想到的可能是Box,Rc,Cell,RefCell这些智能指针,或者干脆什么都不要直接Option<Child<'a>>.

然后我们希望调用

p.set_child();

set_child会生成某个Child实例,且将parent的None改成有内容的Some(child),把None改成Some一定需要&mut p,但我们又想构造出一个一直持有parent引用的Child实例,这里会有冲突(能不能解决我也不懂)。

为了避免&mut p和&p不能同时存在的问题,解决方法是上面的SomeType使用RefCell,而且在最开始初始化Parent的时候,不是设置为None,而是RefCell,这样可以用&p来将None改成Some

最后完整代码如下,Parent中新增了一个id主要看child是不是指向了我们原来的那个Parent.

use std::{cell::RefCell, rc::{Rc, Weak}};
use std::cell::Cell;
use std::marker::PhantomData;
use std::mem::MaybeUninit;

struct Parent<'a> {
    id: i32,
    pub child: RefCell<Option<Child<'a>>>,
}

impl<'a> Parent<'a> {
    pub fn get_value(&self) {
        println!("{}", self.id);
    }

    pub fn set_child(&'a self) {
        let child = Child{ parent: Some(self) };
        self.child.replace(Some(child));
    }

    pub fn run(& self) {
        println!("run");

        if let Some( c) = self.child.borrow().as_ref() {
            c.call_parent();
        }
    }
}

struct Child<'a> {
    pub parent: Option<&'a Parent<'a>>,
}

impl<'a> Child<'a> {
    pub fn call_parent(&self) {
        self.parent.unwrap().get_value();
    }
}

fn main() {

    println!("Hello, world!");
    let p = Parent {id: 123, child: RefCell::new(None)};
    p.set_child();
    p.run();
}

最后这个实现是没有memory leak的,但是限制也比较大,就是p在set_child后不能再调用任何&mut 方法

TinusgragLin 2024-03-09 02:32

@jian 给出的代码似乎还真不存在循环引用:

  1. 首先 main 开始时在栈上创建了一个 Parent (p), 内容为空指针。

  2. 然后 p.set_child() 首先以自身(self)为源复制(self.clone())了一个 Parent 到栈上,然后把它包在 Rc 里又复制到了堆上;这之后又在栈上创建了一个 Child,让 Child.parent 指向堆上的那个 Parent,然后也将这个 Child 包在 Rc 里复制到了堆上,最后让 self.child 指向在堆上的那个 Child。也就是说,set_parent() 实际上创建了另一个新的(在堆上的)Parent,然后创建了一个(在堆上的)指向这个 ParentChild,最后设置 self.child 指向这个 Child

(存在于 main 函数栈帧中) Parent1 -> (存在于堆中) Child1 -> (存在于堆中) Parent2 -> NULL

如果再来一次 set_child(),又会创建新的 Parent3(复制 Parent1 得到) 和 Child2

(存在于 main 函数栈帧中) Parent1 -> (存在于堆中) Child2 -> (存在于堆中) Parent3 -> (存在于堆中) Child1 -> (存在于堆中) Parent2 -> NULL

我觉得楼主的这个问题的一个难点是,Parent.set_child() 调用时需要一个对 Parent 的“已被引用计数的引用”(Rc<Parent>),这就要求 Parent 先被纳入引用计数系统。

P.S. 在我有限的使用 Weak 的经验里,似乎没有出现很多生命周期问题。如果我记得没错的话,用 Rc::downgrade(&rc) 就可以获得一个 Weak;然后使用它的时候用 Weak::upgrade(&weak),如果此时所指对象还在,就可以获得一个 Rc 了。

作者 jasper2007111 2024-03-08 20:26

有一点不明白,这里不存在循环引用吗?我一开始想着用Weak来着,结果最后陷入到各种'a的报错循环中。

--
👇
jian:

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone)]
struct Parent {
    pub child: Option<Rc<RefCell<Child>>>,
}

impl Parent {
    pub fn get_value(&self) {
        println!("30");
    }

    pub fn set_child(&mut self) {
        let child = Child {
            parent: Some(Rc::new(RefCell::new(self.clone()))),
        };
        self.child = Some(Rc::new(RefCell::new(child)));
    }

    pub fn run(&self) {
        println!("run");
        if let Some(c) = &self.child {
            c.borrow().call_parent();
        }
    }
}

struct Child {
    pub parent: Option<Rc<RefCell<Parent>>>,
}

impl Child {
    pub fn call_parent(&self) {
        if let Some(parent_rc) = &self.parent {
            let parent = parent_rc.borrow();
            parent.get_value();
        }
    }
}

fn main() {
    println!("Hello, world!");
    let mut p = Parent { child: None };
    p.set_child();
    p.run();
}

lithbitren 2024-03-03 01:13

可以刷刷题来熟悉熟悉这种Option<Rc<RefCell>>的循环引用模式。

不过也就熟悉熟悉,感觉正儿八经的地方都用unsafe封装一段来用。

作者 jasper2007111 2024-03-02 21:11

多谢,可以运行。

--
👇
jian:

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone)]
struct Parent {
    pub child: Option<Rc<RefCell<Child>>>,
}

impl Parent {
    pub fn get_value(&self) {
        println!("30");
    }

    pub fn set_child(&mut self) {
        let child = Child {
            parent: Some(Rc::new(RefCell::new(self.clone()))),
        };
        self.child = Some(Rc::new(RefCell::new(child)));
    }

    pub fn run(&self) {
        println!("run");
        if let Some(c) = &self.child {
            c.borrow().call_parent();
        }
    }
}

struct Child {
    pub parent: Option<Rc<RefCell<Parent>>>,
}

impl Child {
    pub fn call_parent(&self) {
        if let Some(parent_rc) = &self.parent {
            let parent = parent_rc.borrow();
            parent.get_value();
        }
    }
}

fn main() {
    println!("Hello, world!");
    let mut p = Parent { child: None };
    p.set_child();
    p.run();
}

jian 2024-03-02 19:05
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone)]
struct Parent {
    pub child: Option<Rc<RefCell<Child>>>,
}

impl Parent {
    pub fn get_value(&self) {
        println!("30");
    }

    pub fn set_child(&mut self) {
        let child = Child {
            parent: Some(Rc::new(RefCell::new(self.clone()))),
        };
        self.child = Some(Rc::new(RefCell::new(child)));
    }

    pub fn run(&self) {
        println!("run");
        if let Some(c) = &self.child {
            c.borrow().call_parent();
        }
    }
}

struct Child {
    pub parent: Option<Rc<RefCell<Parent>>>,
}

impl Child {
    pub fn call_parent(&self) {
        if let Some(parent_rc) = &self.parent {
            let parent = parent_rc.borrow();
            parent.get_value();
        }
    }
}

fn main() {
    println!("Hello, world!");
    let mut p = Parent { child: None };
    p.set_child();
    p.run();
}

1 共 12 条评论, 1 页