< 返回版块

skrnoob 发表于 2020-11-16 05:31

来自权威指南的小练习,关于面向对象中的状态模式。

pub struct Post {
    state: Option<Box<dyn State>>,
    content:String,
}

post实例有多个状态,为了限制每个状态下的行为,方法都写在trait State里。比如

impl Post {
   // skip
    pub fn content(&self) -> &str {
        if let Some(s) = &self.state {
            return s.content(self);
        } else {
            return "";
        }
    }
   
}

trait State {
    // skip
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }

}

struct Published {}

impl State for Published {
    // skip
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        &post.content
    }
}

这样调用post.content()的时候,只有state为Published的时候才会去访问content。 现在有个写入content的需求,辣我就写:

impl Post {
   // skip
    pub fn add_text(&mut self,text: &str) {
        self.state.as_mut().unwrap().add_text(self,text);
    }
   
}

impl State for Draft {
    // skip
    fn add_text(&self, post: &mut Post, text:&str) {
        post.content.push_str(text);
    }

}

然后就报错了

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src\lib.rs:15:47
   |
15 |         self.state.as_mut().unwrap().add_text(self,text);
   |         ----------                   -------- ^^^^ second mutable borrow occurs here
   |         |                            |
   |         |                            first borrow later used by call
   |         first mutable borrow occurs here

error: aborting due to previous error

说是引用规则的问题,用state的时候引用了self,然后给add_text传参的self又是一次mut 引用,违反了规则。add_text要通过引用self来获得,而add_text又需要传入self的mut引用,两个引用的scope隔离不开来。 小弟走投无路只想到用RefCell,感觉像耍赖。 小弟在此请教,请问该怎么解决这个问题呢?

评论区

写评论
作者 skrnoob 2020-11-23 22:37

get到了!这种取值出来就不会保留引用,我原本那种链式会一直保留引用。

--
👇
eweca: 我也觉得这个写法有点奇怪,不过官方书TRPL第17.3章节有几乎一模一样的例子。 官网书的写法是直接在impl Post里面写而不是把它写进trait,当然,这会导致所有状态下都可以修改文档:

TRPL里的代码如下:

impl Post {
    // --snip--
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }

    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }
    // --snip--
}

如果你想只能在Draft或某几种状态下写入,才需要按照你这个写法吧?你这个写法的问题是你在不可变借用self.state的情况下还想拿到self的可变借用,解法可以借鉴官方书里函数request_review。 上面已给出这个函数作为模板:

  1. 我们先申请一个Some(mut s)去take掉self.state的option,此时self.state(实质是Post.state)是None,然后此时我们有了一个可变变量s: Box。
  2. 然后我们用s.add_text(self, text)去修改self.content(实质是Post.content)里的内容,此时s是另一个变量,所以我们在这里只借用了self(实质是Post)这一个可变借用。这个trait操作只有在s为特定trait状态下可以实现增加text,默认trait是无事发生。
  3. 使用self.state = Some(s)把这个trait object用Some包装完扔回去。 亲测可行。
impl Post {
    // --snip--
    pub fn add_text(&mut self, text: &str) {
        if let Some(mut s) = self.state.take() {
            s.add_text(self, text);
            self.state = Some(s);
        }
    }
    // --snip--
}

trait State {
    fn request_review(self: Box<Self>) -> Box<dyn State>;

    fn approve(self: Box<Self>) -> Box<dyn State>;

    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }

    fn add_text(&mut self, post: &mut Post, text: &str) {}
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        Box::new(PendingReview {})
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn add_text(&mut self, post: &mut Post, text: &str) {
        post.content.push_str(text);
    }
}
eweca 2020-11-18 21:29

一个小修改,add_text只需要获得不可变借用即可。

fn add_text(&self, post: &mut Post, text: &str) {}
eweca 2020-11-18 21:16

我也觉得这个写法有点奇怪,不过官方书TRPL第17.3章节有几乎一模一样的例子。 官网书的写法是直接在impl Post里面写而不是把它写进trait,当然,这会导致所有状态下都可以修改文档:

TRPL里的代码如下:

impl Post {
    // --snip--
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }

    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }
    // --snip--
}

如果你想只能在Draft或某几种状态下写入,才需要按照你这个写法吧?你这个写法的问题是你在不可变借用self.state的情况下还想拿到self的可变借用,解法可以借鉴官方书里函数request_review。 上面已给出这个函数作为模板:

  1. 我们先申请一个Some(mut s)去take掉self.state的option,此时self.state(实质是Post.state)是None,然后此时我们有了一个可变变量s: Box。
  2. 然后我们用s.add_text(self, text)去修改self.content(实质是Post.content)里的内容,此时s是另一个变量,所以我们在这里只借用了self(实质是Post)这一个可变借用。这个trait操作只有在s为特定trait状态下可以实现增加text,默认trait是无事发生。
  3. 使用self.state = Some(s)把这个trait object用Some包装完扔回去。 亲测可行。
impl Post {
    // --snip--
    pub fn add_text(&mut self, text: &str) {
        if let Some(mut s) = self.state.take() {
            s.add_text(self, text);
            self.state = Some(s);
        }
    }
    // --snip--
}

trait State {
    fn request_review(self: Box<Self>) -> Box<dyn State>;

    fn approve(self: Box<Self>) -> Box<dyn State>;

    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }

    fn add_text(&mut self, post: &mut Post, text: &str) {}
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        Box::new(PendingReview {})
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn add_text(&mut self, post: &mut Post, text: &str) {
        post.content.push_str(text);
    }
}
作者 skrnoob 2020-11-16 18:23

确实有一种递归改动的赶脚,不太科学

--
👇
Mike Tang: rust 中一般不会/不应出现

self.foo(self, bar);

这样的代码。 搞复杂了吧。

作者 skrnoob 2020-11-16 18:21

是书上的教例,在讲面向对象的状态模式,所以有丶像java吧可能

--
👇
shaitao: 不需要像java那么蹩脚的

pub struct Published(String);
pub struct Draft(String);


impl Published {
    pub fn add_text(&mut self, text: &str) {
        self.0.push_str(text);
    }
}

impl Draft {
    pub fn add_text(&mut self, text: &str) {
        self.0.push_str(text);
    }
    
    pub fn publish(self)-> Published {
        Published(self.0)
    }
}
作者 skrnoob 2020-11-16 18:19

有一说一,确实

--
👇
Ryan-Git: 这代码写出来保管一个礼拜后就看不懂了,太绕了

Ryan-Git 2020-11-16 14:14

这代码写出来保管一个礼拜后就看不懂了,太绕了

Mike Tang 2020-11-16 11:09

rust 中一般不会/不应出现

self.foo(self, bar);

这样的代码。 搞复杂了吧。

shaitao 2020-11-16 11:06

不需要像java那么蹩脚的

pub struct Published(String);
pub struct Draft(String);


impl Published {
    pub fn add_text(&mut self, text: &str) {
        self.0.push_str(text);
    }
}

impl Draft {
    pub fn add_text(&mut self, text: &str) {
        self.0.push_str(text);
    }
    
    pub fn publish(self)-> Published {
        Published(self.0)
    }
}
1 共 9 条评论, 1 页