Struggling to understand the reason for move out of borrowed context and correct lifetime assignment

I'm trying to get the following test to pass but am struggling to (a) annotate the necessary lifetimes and (b) understand where I am trying to "move out of borrowed context":

#[test]
fn entity_builder_associates_component_with_entity() {
    let mut entity_manager = EntityManager::new();
    let entity_id = entity_manager
        .new_entity()
        .add_component(Transform { x: 0.0, y: 0.0 })
        .create();
    entity_manager.update();

    if let Some(found_id) = entity_manager.entities_with("transform").get(0) {
        assert_eq!(found_id, &entity_id);
    } else {
        panic!(false);
    }
}

Which currently gives me the following error:

error[E0507]: cannot move out of borrowed content
   --> src/core/entity.rs:198:25
    |
198 |           let entity_id = entity_manager
    |  _________________________^
199 | |             .new_entity()
200 | |             .add_component(transform)
    | |_____________________________________^ cannot move out of borrowed content

I'll post what I think is the relevant implementation code below. I'm struggling to understand why this error occurs because as far as I can tell the transform is owned and is being consumed by the add_component method.

I get that once pushed onto the vec it is then borrowed, which is where I'm having difficulty specifying lifetimes. I think I need a 'b on the components vec and possibly in the signature of add_component. But I think I might be having issues due to wanting EntityManager eventually own the Components (which is just Transform for now), which the EntityBuilder initially owns, therefore I think the components need a lifetime of at least as long as EntityManager, but because EntityBuilder has a reference to the manager, that also needs a similarly long lifetime, and as you can see, I've confused myself considerably!

Any help greatly appreciated.

Now for the implementation code as it stands:

type EntityId = usize;

enum Operation {
    AddEntity(EntityId, Vec<Transform>),
    RemoveEntity(EntityId, usize),
}

struct EntityBuilder<'a> {
    entity_manager: &'a mut EntityManager,
    entity_id: EntityId,
    components: Vec<Transform>,
}

impl<'a> EntityBuilder<'a> {
    fn new(entity_manager: &'a mut EntityManager) -> EntityBuilder {
        let entity_id = entity_manager.next_id();
        EntityBuilder {
            entity_manager,
            entity_id,
            components: vec![],
        }
    }

    pub fn add_component(&mut self, component: Transform) -> &'a mut EntityBuilder {
        self.components.push(component);
        self
    }

    pub fn create(self) -> EntityId {
        self.entity_manager.add_entity(self.entity_id, self.components);
        self.entity_id
    }
}

struct EntityManager {
    next_id: EntityId,
    next_entity_index: usize,
    entities: Vec<EntityId>,
    entity_indexes: HashMap<EntityId, usize>,
    components: HashMap<String, Transform>,
    operations: Vec<Operation>,
}

impl EntityManager {
    pub fn new() -> EntityManager {
        EntityManager { ... }
    }

    pub fn new_entity(&mut self) -> EntityBuilder {
        EntityBuilder::new(self)
    }

    pub fn entities_with(&self, component_type: &str) -> Vec<EntityId> {
        vec![-1]
    }

    pub fn update(&mut self) {
        while !self.operations.is_empty() {
            match self.operations.pop().unwrap() {
                AddEntity(entity_id, components) => self.save_entity(entity_id, components),
            }
        }
    }

    fn next_id(&mut self) -> EntityId { // Increment and return next_id + 1 }

    fn add_entity(&mut self, entity_id: EntityId, components: Vec<Transform>) {
        self.operations.push(AddEntity(entity_id, components));
    }

    fn save_entity(&mut self, entity_id: EntityId, mut components: Vec<Transform>) {
        ...
        self.components.insert(String::from("transform"), components.pop().unwrap());
    }
}

I don't see the line reported in the error anywhere in the code you posted. Can you put your code on https://play.rust-lang.org?

Also, your EntityBuilder::add_component function signature seems a little odd to me. Maybe reading The builder pattern will help you.

Of course. Here's the link to a full copy and paste rather than trying to only show relevant items.

I'll take a look at the link on builders, thanks.

You're defining the implementation of EntityBuilder<'a>, if you are going to return a new EntityBuilder you need to specify the lifetime. In this case you're returning self so it would be EntityBuilder<'a>:

    pub fn add_component(mut self, component: Transform) -> EntityBuilder<'a> {

I would use Self as the return type to be more clear that its just returning an instance of the same thing.

Side note: Make sure you run the code you paste in the playground. I had to fix an import statement for it to work as intended. Good luck with your project.

"Cannot move out of borrowed context" error has nothing to do with lifetimes, and can't be fixed by changing lifetimes.

It's a situation where you have a reference to an object, and you're trying to own it (it's stealing!)

For example, if you have &[String], it's a borrowed context (a borrowed slice), and String needs to be moved, and you can't do:

let s = slice[1];

because you don't own the slice, so you're not allowed to modify or destroy it, but you're trying to move an object, which would require destroying it at its previous location.

I can't follow exactly what happens in your code example, so I can't tell you exactly where your code does this. Look for things that you access have access to by reference, and make sure you're not trying to remove anything from them.

Specifically, for Option the solution is to call .as_ref() or .take() if it's a mutable reference. In other cases you can effectively move data out of borrowed context by swapping it mem::swap/mem::replace with another instance, so that you don't leave a "hole" behind the moved object.

Code was changed in between first post and playground link. Replied to the playground link one because that was the most current :slight_smile:

Thanks. That's really helpful. It's funny, I was originally returning Self from EntityBuilder::new() but changed it to EntityBuilder before I started on adding components. Had I not, I'd never have had this problem, and potentially would have lost out on some learning.

as_ref() and take() is useful to know, thanks.

My reason for including lifetimes is that if I wasn't having trouble sorting them out, I think I'd have found it easier to see where I was trying to move something that I didn't own. I see that my conflating the 2 concepts is confusing, however.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.