Issue understanding lifetimes

Hello all,

I'm trying to learn Rust and in order to do so I'm trying to write an n body simulation (originally implemented/prototypes in Python, but it doesn't scale well at all).

The issue is I can't seem to get the same structure of code to work in Rust as it does in Python. I believe my understanding of Lifecycles in Rust is my weak point here.

A stripped down example of the Rust and Python versions (tried to make it a 1-1 comparison),
Python:

from typing import List
from dataclasses import dataclass
import random

@dataclass
class Position:
    x: float
    y: float

@dataclass
class Entity:
    position: Position
    
    def __init__(self):
        self.position = Position(
            x=round(random.random(), 4), 
            y=round(random.random(), 4)
        )    

@dataclass
class EntityNearestNeighbors:
    entity: Entity
    nearest_neighbours: List[Entity]
    
    def __init__(self, entity: Entity):
        self.entity = entity
        
    def set_closest_nearest_neighbours(self, all_entities):
        
        # calculate the distances
        distances_with_entity = []
        for e in all_entities:
            dist = distance_between_positions(self.entity.position, e.position)
            dist_with_entity = (dist, e)
            distances_with_entity.append(dist_with_entity)
        # sort by distance
        distances_with_entity.sort(key=lambda x: x[0])
        
        # extract out entities from nearest to furthest
        nearest_neighbors = []
        for dist, e in distances_with_entity:
            nearest_neighbors.append(e)
        
        # set the ordered entities
        self.nearest_neighbours = nearest_neighbors
        
    
def distance_between_positions(pos_1: Position, pos_2: Position) -> float:
    x_dist = pos_1.x - pos_2.x
    y_dist = pos_1.y - pos_2.y
    distance = (x_dist ** 2 + y_dist ** 2) ** 0.5
    return distance

    
if __name__ == '__main__':
    # toy example, so only making 3 entites
    number_of_entities = 3
    all_entities = [Entity() for _ in range(number_of_entities)]
    # print(all_entities)
    
    all_entity_areas = [EntityNearestNeighbors(e) for e in all_entities]
    
    for entity_area in all_entity_areas:
        entity_area.set_closest_nearest_neighbours(all_entities)
    print(f"Entity: {all_entity_areas[0].entity}, nearest entity: {all_entity_areas[0].nearest_neighbours[0]}")

Rust:

use rand::prelude::*;

#[derive(Debug)]
struct Position {
    x: f32,
    y: f32,
}

#[derive(Debug)]
struct Entity {
    position: Position,
}

struct EntityNearestNeighbors<'a> {
    entity: Entity,
    nearest_neighbours: Vec<&'a Entity>,
}

fn distance_between_positions(pos_1: &Position, pos_2: &Position) -> f32 {
    let x_dist = pos_1.x - pos_2.x;
    let y_dist = pos_1.y - pos_2.y;
    let distance = f32::powf(f32::powf(x_dist, 2.) + f32::powf(y_dist, 2.), 0.5);
    distance
}

impl Entity {
    fn new() -> Entity {
        let position = Position {
            y: random(),
            x: random(),
        };
        Entity {
            position: position,
        }
    }
}

impl EntityNearestNeighbors<'_> {
    fn new(entity: Entity) -> EntityNearestNeighbors<'static> {
        let position = Position {
            y: random(),
            x: random(),
        };
        EntityNearestNeighbors {
            entity: entity,
            nearest_neighbours: vec![],
        }
    }

    fn set_closest_nearest_neighbours(&mut self, all_entities: Vec<&Entity>) {

        // calculate the distances
        let mut distances_with_entity: Vec<(f32, &Entity)> = Vec::new();
        for e in all_entities {
            let dist = distance_between_positions(&self.entity.position, &e.position);
            let dist_with_entity = (dist, e);
            distances_with_entity.push(dist_with_entity);
        }
        // sort by distance
        distances_with_entity.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());

        // extract out entities from nearest to furthest
        let nearest_neighbours = vec![];
        for (dist, e) in distances_with_entity.iter() {
            nearest_neighbours.push(*e);
        }

        // set the ordered entities
        self.nearest_neighbours = nearest_neighbours;
    }
}

fn main() {
    // toy example, so only making 3 entities
    let number_of_entities = 3;
    let all_entities = (1..(number_of_entities+1)).map(|_| Entity::new()).collect::<Vec<Entity>>();
    println!("{:?}", all_entities);

    let mut all_entity_areas = all_entities.iter().map(|e| EntityNearestNeighbors::new(*e)).collect::<Vec<EntityNearestNeighbors>>();
    let all_entities_references = all_entities.iter().collect();

    for entity_area in all_entity_areas.iter() {
        entity_area.set_closest_nearest_neighbours(all_entities_references);
    }
    println!("Entity: {:?}, nearest entity: {:?}", all_entity_areas[0].entity, all_entity_areas[0].nearest_neighbours[0]);
}

The compiler is giving this error:

   Compiling parallel_struct_test v0.1.0 (/home/sampur01/Downloads/leetcode/parallel_struct_test)
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:54:18
   |
54 |         for e in all_entities {
   |                  ^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 50:68...
  --> src/main.rs:50:68
   |
50 |     fn set_closest_nearest_neighbours(&mut self, all_entities: Vec<&Entity>) {
   |                                                                    ^^^^^^^
note: ...but the lifetime must also be valid for the lifetime `'_` as defined on the impl at 38:29...
  --> src/main.rs:38:29
   |
38 | impl EntityNearestNeighbors<'_> {
   |                             ^^
note: ...so that the expression is assignable
  --> src/main.rs:54:18
   |
54 |         for e in all_entities {
   |                  ^^^^^^^^^^^^
   = note: expected `Vec<&Entity>`
              found `Vec<&Entity>`

For more information about this error, try `rustc --explain E0495`.
error: could not compile `parallel_struct_test` due to previous error

I'm struggling to understand the issue, because surely the lifetime of the reference to each entity in entity_area would only exist for as long as entity_area exists, which in this case is until the end of the program.
Any advice on what I'm doing wrong would be greatly appreciated.

Playground link: Rust Playground

The point is that, you aren't specifying the lifetime relations sufficiently - which means that since you are using lifetime elision, the compiler makes conservative assumptions and complains.
The gist of the fix is:

impl<'entity> EntityNearestNeighbors<'entity> {
    fn set_closest_nearest_neighbours(&'entity mut self, all_entities: Vec<&'entity Entity>) {
        // Notice how the lifetime specifiers constrain all_entities and self references to have
       // compatible lifetimes.

        // calculate the distances
        let mut distances_with_entity: Vec<(f32, &Entity)> = Vec::new();
        for e in all_entities {
            let dist = distance_between_positions(&self.entity.position, &e.position);
            let dist_with_entity = (dist, e);
            distances_with_entity.push(dist_with_entity);
        }
        // sort by distance
        distances_with_entity.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());

        // extract out entities from nearest to furthest
        let mut nearest_neighbours = vec![];
        for (_, e) in &mut distances_with_entity {
            nearest_neighbours.push(*e);
        }

        // set the ordered entities
        self.nearest_neighbours = nearest_neighbours;
    }
}

An object can only be owned by one thing. If you want EntityNearestNeighbors to own its Entity by-value, then you can't also have the entity somewhere else (unless you clone or similar). Your current snippet of code is trying to also store the entities in all_entities. Beyond that, you also have a bunch of uses of the dereference-operator * on the Entity type, which you also cannot do because that would require taking ownership of the entity.

Additionally, immutable references really make things immutable. From the creation until the last use of an immutable reference, that value may not move, and also may not change. It's not possible to modify a value that an immutable reference exists to in any way — this includes not going through that particular immutable reference.

It is possible to get your code to compile like this. However, as I mentioned, this will make all of your entities immutable for the duration of the EntityNearestNeighbors existing because in that duration, an immutable reference exists to each entity. If you want to be able to change the entities while an EntityNearestNeighbors exists, then you simply cannot use references. (Mutable references wont work either — they must be unique.) One common alternative is indexes into a vector or other types of IDs.

@RedDocMD Putting the lifetime annotated on the struct on &mut self like that is not going to work. That's a classic mistake and always wrong. It will make it virtually impossible to call the method, and in the cases where you can call it, the struct cannot ever be used again after the call.

1 Like

Thanks for pointing this out! I get my mistake now.

Thank you very much for the fast replies!

I have reimplemented it using a Hashmap and giving each Body a unique ID as the key to the Hashmap and it is much easier to understand :+1:.

Also, Rust is soooo fast! I was not expecting this kind of speed!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.