rust 为什么我们需要为Option变量调用take()< T>

k2arahey  于 2023-03-02  发布在  其他
关注(0)|答案(4)|浏览(515)

在这段代码中:

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

impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }
    
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
    
    pub fn content(&self) -> &str {
        ""
    }

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

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

struct Draft {}

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

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

存在对take()的调用;书上说:
要使用旧的状态,request_review方法需要取得状态值的所有权,这就是Post的state字段中的Option的作用所在:我们调用take方法将Some值从state字段中取出,并在其位置上保留None。
我们需要暂时将state设置为None,而不是直接用self.state = self.state.request_review();这样的代码来获得state值的所有权,这样可以确保Post在我们将其转换为新state后不能使用旧的state值。
如果我们直接设置Post,它怎么可能使用它的旧状态呢?

0s7z1bwu

0s7z1bwu1#

如果你这样写代码:

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

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

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

您将得到一个编译器错误:

self.state = self.state.request_review();
             ^^^^^^ move occurs because `self.state` has type `std::boxed::Box<dyn State>`, which does not implement the `Copy` trait'.

这是因为调用State::request_review会移动Box<self>,它是在heap上分配的,Rust不允许你从heap中移走值,除非你实现了Copy,否则那里还剩下什么?本书使用Option::take()来移出所有权,并将None留在原处。

ymzxtsji

ymzxtsji2#

如果request_review死机,将导致释放Box两次,第一次是在request_review中,第二次是在释放Option时。

nbnkbykc

nbnkbykc3#

引自书中;粗体我的格式
我们调用take方法从state字段中取出Some值并在其位置保留None,因为Rust不允许我们在结构体中有未填充的字段
至于同一语句中的移动和赋值,看起来Rust没有检测到它是有效的。

hgncfbus

hgncfbus4#

我不认为数据在堆上的事实,正如@xiang-zhou提到的,是这里的基本问题,下面的代码不能编译,因为对Blog::request_review的调用试图将state字段移出self

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

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

但是,编译器不允许这样做,因为selfself.state在引用之后,这对于堆栈上的数据也是一个问题:

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

为了解决这个问题,我们使用Option::take,因为它允许从&mut引用中移出一个值,这通常是不允许的。Option::takemem::replace的源代码依赖于精心实现的unsafe rust来实现这一点。

相关问题