< 返回版块

Aaron009 发表于 2021-05-01 14:05

Tags:Option

这里为什么要Option,看完教程还是不懂?

教程:

https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html#defining-post-and-creating-a-new-instance-in-the-draft-state

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

这里为什么需要Option呢? 通过观察整个源码得知,这里state是不可能为空,所以根据我的理解这里是不需要用到Option的。

但是当我把Option去掉时,出现以下情况。

pub fn request_review(&mut self) {
    // if let Some(t) = self.state.take() {
    //     self.state = Some(t.request_review())
    // }
    self.state = self.state.request_review();
}

编译提示:

error[E0507]: cannot move out of `self.state` which is behind a mutable reference
  --> src\lib.rs:26:22
   |
26 |         self.state = self.state.request_review();
   |                      ^^^^^^^^^^ move occurs because `self.state` has type `Box<dyn State>`, which does not implement the `Copy` trait

self.state 这里发生了移动,那我该如何解决这个问题呢,解决这个问题有几种解决方式?

还有一个问题,Option使用场景有哪些?

完整源码

main.rs

use oop_exercise::Post;

fn main() {
    let t = "I love China!";
    let mut post = Post::new();
    post.add_content(t);
    assert_eq!("", post.content());

    post.request_review();
    assert_eq!("", post.content());

    post.approve();
    assert_eq!(t, post.content());
}

lib.rs

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

impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(DraftPost {})),
            content: String::new()
        }
    }

    pub fn add_content(&mut self, content: &str) {
        self.content.push_str(content);
    }

    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(self)
    }

    pub fn request_review(&mut self) {
        if let Some(t) = self.state.take() {
            self.state = Some(t.request_review())
        }
    }

    pub fn approve(&mut self) {
        if let Some(t) = self.state.take() {
            self.state = Some(t.approve())
        }
    }
}

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

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

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

struct DraftPost {
}

struct PendingReviewPost {
}

struct PublishedPost {
}

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

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

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

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

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

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

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

评论区

写评论
modraedlau 2021-05-02 20:55

这篇教程主要从OO的角度去实现,上面的朋友也解释了为什么用Option,注意这里不是不会出现None,在self.state.take()后,在self.state = ...之前post中的state是存在None的情况。

另:教程从动态的角度去设计State,如果从可读性和实用的角度,实际上这里使用enum会更合理:

pub struct Post {
    state: State,
    content: String,
}

impl Post {
    pub fn new() -> Post {
        Post {
            state: State::Draft,
            content: String::new(),
        }
    }

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

    pub fn content(&self) -> &str {
        self.state.content(self)
    }

    pub fn request_review(&mut self) {
        self.state = self.state.request_review();
    }

    pub fn approve(&mut self) {
        self.state = self.state.approve();
    }
}

#[derive(Clone, Copy)]
enum State {
    Draft,
    PendingReview,
    Published,
}

impl State {
    fn request_review(self) -> State {
        if let State::Draft = self {
            return State::PendingReview;
        }
        return self;
    }

    fn approve(self) -> State {
        if let State::PendingReview = self {
            return State::Published;
        }
        return self;
    }

    fn content<'a>(&self, post: &'a Post) -> &'a str {
        if let State::Published = self {
            return &post.content;
        }
        ""
    }
}

注意:设计State采用了Copy语义(State此时的大小只有一个字节,其实和u8一样)

苦瓜小仔 2021-05-01 22:06

楼上是正解~

关键在于 state 字段的值需要改变,且这个改变发生在 lib crate 中,而无需 使用者更改。

我试着用 RefCell 达到内部可变性,从而替代了 Option。当然,从效率上说,肯定 Option 的方式更高。

// src/lib.rs
use std::cell::RefCell;

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

impl Post {
    pub fn new() -> Post {
        Post { state: RefCell::new(Box::new(DraftPost {})), content: String::new() }
    }

    pub fn add_content(&mut self, content: &str) { self.content.push_str(content); }

    pub fn content(&self) -> &str { self.state.borrow().content(self) }

    pub fn request_review(&mut self) {
        let new_state = self.state.borrow().request_review();
        let new_state = RefCell::new(new_state);
        self.state.swap(&new_state);
    }

    pub fn approve(&mut self) {
        let new_state = self.state.borrow().approve();
        let new_state = RefCell::new(new_state);
        self.state.swap(&new_state);
    }
}

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

    fn request_review(&self) -> Box<dyn State>;

    fn approve(&self) -> Box<dyn State>;
}

struct DraftPost {}

struct PendingReviewPost {}

struct PublishedPost {}

impl State for DraftPost {
    fn request_review(&self) -> Box<dyn State> { Box::new(PendingReviewPost {}) }

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

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

    fn approve(&self) -> Box<dyn State> { Box::new(PublishedPost {}) }
}

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

    fn request_review(&self) -> Box<dyn State> { Box::new(Self {}) }

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

--
👇
Aya0wind: 因为这两个操作是不一样的,你看看Option::take()的文档里写了这句话
Takes the value out of the option, leaving a [None] in its place. 用了Option你才可以把使用&mut self把state的值move出来,然后在原来的位置留一个None,不用Option你是无法move一个已经被borrow的对象的成员的。 Option应用场景那太广了,只要是表示可空的语义都可以用,你这个例子其实也是因为state可空,因为你需要使用一个mut借用来move其成员。如果不用Option,state被你move走了,而你却只有一个self的借用,并不能保证出了函数之后self就不会被使用了。那么势必产生一个使用被move了的对象的可能性,放到C/C++里就是use after move的错误。而用Option就可以留一个None值在里面,外面使用它就需要判空了。

Aya0wind 2021-05-01 14:25

因为这两个操作是不一样的,你看看Option::take()的文档里写了这句话
Takes the value out of the option, leaving a [None] in its place. 用了Option你才可以把使用&mut self把state的值move出来,然后在原来的位置留一个None,不用Option你是无法move一个已经被borrow的对象的成员的。 Option应用场景那太广了,只要是表示可空的语义都可以用,你这个例子其实也是因为state可空,因为你需要使用一个mut借用来move其成员。如果不用Option,state被你move走了,而你却只有一个self的借用,并不能保证出了函数之后self就不会被使用了。那么势必产生一个使用被move了的对象的可能性,放到C/C++里就是use after move的错误。而用Option就可以留一个None值在里面,外面使用它就需要判空了。

1 共 3 条评论, 1 页