Implement search function / return reference

Hello all,

I am having a problem getting around implementing a search function which returns a reference of a HashMap value. I've simplified the problem with the following code:

use std::cell::RefCell;
use std::collections::HashMap;

struct Student {
    id: u8,
}

struct Department {
    students: HashMap<String, Student>,
}

struct University {
    departments: RefCell<HashMap<String, Department>>,
}

fn main() {
    let university = University {
        departments: RefCell::new(HashMap::new()),
    };
    university.departments.borrow_mut().insert(
        String::from("IT"),
        Department {
            students: HashMap::new(),
        },
    );
    university.departments.borrow_mut().insert(
        String::from("BIO"),
        Department {
            students: HashMap::new(),
        },
    );
    university
        .departments
        .borrow_mut()
        .get_mut("IT")
        .unwrap()
        .students
        .insert(String::from("test-name"), Student { id: 1 });
    let _ = search_student(university, String::from("test-name"));
}

fn search_student<'a>(university: University, name: String) -> Option<&'a Student> {
    let departments = university.departments.borrow();
    departments.get("IT").unwrap().students.get(&name)
}

As you can see, search_student is given a "key" to search for which is used to fetch the student given a name. Trying to build this code, i get:

error[E0515]: cannot return value referencing local variable `departments`
  --> src/main.rs:44:5
   |
44 |     departments.get("IT").unwrap().students.get(&name)
   |     -----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     `departments` is borrowed here

error[E0515]: cannot return value referencing local data `university.departments`
  --> src/main.rs:44:5
   |
43 |     let departments = university.departments.borrow();
   |                       ---------------------- `university.departments` is borrowed here
44 |     departments.get("IT").unwrap().students.get(&name)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

While i realize that the department value is owned by the university instance and each student is also owned by it's department, the get call i am using returns a reference to the Student object and that is exactly what i want. As far as i can tell, the reference to Student is not owned by the current function, the variable department is.

In short, i am trying to build this search function to do exactly what the signature does. I do not wish to unwrap the option and return a clone of the Student value because i am dealing with a large instance here that i do not want to clone. So returning a reference would work best.

Any help would be appreciated.

The first problem is search_student not taking reference but returning a reference. You cannot create 'a lifetime out of thin air. You probably meant to take university as a reference as you probably want to search for more than one thing from one university. Also, for name, a reference (unrelated lifetime to university reference) is enough.

Then, the second problem is you are borrowing RefCell inside the function but the "guard" (std::cell::Ref type) of the borrow is dropped within the function. A solution is moving RefCell to the outer level. This changes the type definition but using RefCell here is questionable (context needed).

fn search_student<'a, 'b>(university: &'a University, name: &'b str) -> Option<&'a Student> {
    university.departments.get("IT").unwrap().students.get(name)
}

There are two problems:

  1. search_student is given the ownership of university and it will be dropped as soon as the function returns. Accessing it afterwards is invalid and not safe and therefore the compiler will not allow the same. This can be fixed by altering the search function to borrow the university instead as follows:
fn search_student<'a>(
    university: &'a University, name: String
) -> Option<&'a Student>;
  1. The same problem as above but for the local borrow() which does not outlive the function search_student on the line
let departments = university.departments.borrow();

Unfortunately, there is no straight-forward work around for this situation and, depending on your use case, you may want reconsider the use of RefCell or re-arch your code. If you do want to stick with RefCell, see this SO answer.

Here is a playground of your code without using RefCell.

1 Like