【问题标题】:Cannot move out of borrowed content and Builder pattern无法移出借用内容和构建器模式
【发布时间】:2015-08-06 14:58:12
【问题描述】:

我只是在学习 Rust。我正在尝试为我的 Game 结构创建一个构建器结构。代码如下:

struct Input {
    keys_pressed: HashMap<VirtualKeyCode, bool>,
}

pub struct GameBuilder {
    settings: GameSettings,
    input: Input,
}

impl GameBuilder {
    pub fn new() -> GameBuilder {
        GameBuilder {
            settings: GameSettings {
                window_dimensions: (800, 600),
                title: "".to_string(),
            },
            input: Input {
                keys_pressed: HashMap::new(),
            }
        }
    }

    pub fn with_dimensions(&mut self, width: u32, height: u32) -> &mut GameBuilder {
        self.settings.window_dimensions = (width, height);
        self
    }

    pub fn with_title(&mut self, title: &str) -> &mut GameBuilder {
        self.settings.title = title.to_string();
        self
    }

    pub fn game_keys(&mut self, keys: Vec<VirtualKeyCode>) -> &mut GameBuilder {
        for key in keys {
            self.input.keys_pressed.insert(key, false);
        }
        self
    }

    pub fn build(&self) -> Game {
        let (width, height) = self.settings.window_dimensions;
        Game {
            display: glutin::WindowBuilder::new()
                        .with_dimensions(width, height)
                        .with_title(self.settings.title.to_string())
                        .build_glium()
                        .ok()
                        .expect("Error in WindowBuilder"),
            state: GameState::Running,
            input: self.input,
        }
    }
}

但这段代码在最后一行 input: self.input 抱怨:

error: cannot move out of borrowed content

我想我明白为什么了。由于函数中传递的参数是&amp;self,因此我无法获得它的所有权,而最后一行正在做什么。

我认为也许将&amp;self 更改为self 会起作用,但编译后认为我不能改变self

据我所知,还有 Copy 特征,这也许应该可以解决问题。但 Input 基本上是一个 HashMap,这意味着如果散列本身太大,副本可能会很昂贵。

解决这个问题的好方法是什么?

编辑:

我试过这样做:

#[derive(Debug, Copy, Clone)]
struct Input {
    keys_pressed: HashMap<VirtualKeyCode, bool>,
}

但是编译器抱怨:

error: the trait `Copy` may not be implemented for this type; field `keys_pressed` does not implement `Copy`

【问题讨论】:

  • Copy trait 只能用于可简单复制的类型,这不需要任何内部堆分配内存。
  • 看来我不能为 Input 做到这一点,因为 HashMap 没有实现Copy
  • @ker:老实说,如果 OP 将代码包含在链式调用中,那就更明显了!
  • MCVE 让世界变得如此简单......
  • @lhahn: MCVE;但请放心,我们都知道设法充分减少代码并仍然出现问题并不总是显而易见的。

标签: rust borrow-checker


【解决方案1】:

鉴于您的方法签名是如何制定的,您的目标似乎是链接:

let game = GameBuilder::new().with_dimensions(...)
                             .with_title(...)
                             .build();

在 Rust 中,这要求 GameBuilder 按值传递:

pub fn with_dimensions(self, ...) -> GameBuilder {
    // ...
}

为了能够在方法内改变self,您需要使其成为mut

pub fn with_dimensions(mut self, ...) -> GameBuilder {
}

如果您将with_dimensionswith_titlegame_keysbuild 的签名更改为按值获取self(如果打算进行更改,则为mut self),那么链接应该可以工作。

【讨论】:

  • 啊我没有看到链接...这解释了错误来自哪里
  • 您可以使用&amp;mut self 方法并返回&amp;mut Self。它要求您在构建器内的非复制值上使用clone(),或者将非复制值包装在Option 中,然后将take() 包装出构建器。
【解决方案2】:

使用Optiontake() 尝试构建器模式

例子:

#[derive(PartialEq, Debug)]
struct Game {
    window: Window,
}

#[derive(PartialEq, Debug)]
struct Window {
    title: String,
    dimensions: (u32, u32),
}

struct GameBuilder {
    window_title: Option<String>,
    window_dimensions: Option<(u32, u32)>,
}

impl GameBuilder {
    fn new() -> Self {
        Self {
            window_title: None,
            window_dimensions: None,
        }
    }

    fn window_title(&mut self, window_title: &str) -> &mut Self {
        self.window_title = Some(window_title.to_owned());
        self
    }

    fn window_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
        self.window_dimensions = Some((width, height));
        self
    }

    fn build(&mut self) -> Result<Game, Box<dyn std::error::Error>> {
        Ok(Game {
            window: Window {
                // `ok_or(&str)?` works, because From<&str> is implemented for Box<dyn Error>
                title: self.window_title.take().ok_or("window_title is unset")?,
                dimensions: self
                    .window_dimensions
                    .take()
                    .ok_or("window_dimensions are unset")?,
            },
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let mut builder = GameBuilder::new();
        builder.window_title("Awesome Builder");
        builder.window_dimensions(800, 600);
        let game = builder.build();

        assert_eq!(
            game.expect("build success"),
            Game {
                window: Window {
                    title: "Awesome Builder".into(),
                    dimensions: (800, 600)
                }
            }
        );
    }

    #[test]
    fn test_1() {
        let game2 = GameBuilder::new()
            .window_title("Busy Builder")
            .window_dimensions(1234, 123)
            .build();

        assert_eq!(
            game2.expect("build success"),
            Game {
                window: Window {
                    title: "Busy Builder".into(),
                    dimensions: (1234, 123),
                }
            }
        )
    }
}

【讨论】:

    猜你喜欢
    • 2018-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-25
    • 2017-10-15
    • 1970-01-01
    • 2015-04-20
    • 1970-01-01
    相关资源
    最近更新 更多