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

Moh*_*adi 8 rust

在这段代码中:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

有一个电话take();这本书说:

要使用旧状态, request_review 方法需要获取状态值的所有权。这就是 Post 的 state 字段中的 Option 的用武之地:我们调用 take 方法从 state 字段中取出 Some 值,并在其位置留下一个 None 。

我们需要暂时将 state 设置为 None 而不是直接用代码设置它,比如self.state = self.state.request_review();获取 state 值的所有权。这确保 Post 在我们将其转换为新状态后不能使用旧状态值。

Post如果我们直接设置它,怎么可能使用它的旧状态?

小智 7

如果你这样编码:

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();
    }
    // ... 
}
Run Code Online (Sandbox Code Playgroud)

你会得到一个编译器错误:

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();
    }
    // ... 
}
Run Code Online (Sandbox Code Playgroud)

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


小智 5

我不认为数据在堆上这一事实,正如@fang-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 {})
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,编译器不允许这样做,因为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
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,Option::take使用它是因为它允许将值从&mut引用中移出,而这通常是不允许的。源代码Option::take并依赖mem::replace于精心实施的不安全 Rust 来实现这一点。