Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot borrow in a Rc as mutable

First of all I'm new with Rust :-)

The problem: I want to create a module called RestServer that contain the methods ( actix-web ) to add routes and start the server.

struct Route
{
   url: String,
   request: String,
   handler: Box<dyn Fn(HttpRequest) -> HttpResponse>
}


impl PartialEq for Route {
   fn eq(&self, other: &Self) -> bool {
     self.url == other.url
   }
}

impl Eq for Route {}

impl Hash for Route {
   fn hash<H: Hasher>(&self, hasher: &mut H) {
      self.url.hash(hasher);
   }
}

this is the route structure, this structure containe the the route url, the request type ( GET, POST etc ) and hanlder is the function that have to catch the request and return a HTTPResponse

pub struct RestServer
{
   scopes: HashMap<String, Rc<HashSet<Route>>>,
   routes: HashSet<Route>,
   host: String,
}

impl RestServer {

   pub fn add_route(self, req: &str, funct: impl Fn(HttpRequest) -> HttpResponse + 'static,
                 route: &str, scope: Option<&str>) -> RestServer
   {
       let mut routes_end = self.routes;
       let mut scopes_end = self.scopes;
       let url = self.host;
       let route = Route {
          url: String::from(route),
          request: String::from(req),
          handler: Box::new(funct)
    };

    if let Some(x) = scope {
        if let Some(y) = scopes_end.get(x) {
            let mut cloned_y = Rc::clone(y);
            cloned_y.insert(route);
            scopes_end.insert(String::from(x), cloned_y);
        }else {
            let mut hash_scopes = HashSet::new();
            hash_scopes.insert(route);
            scopes_end.insert(String::from(x), Rc::new(hash_scopes));
        }
    } else {
        routes_end.insert(route);
    }

    RestServer {
        scopes: scopes_end,
        routes: routes_end,
        host: String::from(url)
    }
  }

the latest code is the implementation of RestServer. The most important part is the add_route function, this function receive as paramente the route that is a string, the function handler, the request string and the scope. First i create the route object. I check if the scope exist into the HashMap, if yes i have to take the actual scope and update the HashSet.

When i build the code i get the following error

   error[E0596]: cannot borrow data in an `Rc` as mutable
   --> interface/src/rest/mod.rs:60:17
   |
60 |                 cloned_y.insert(route);
   |                 ^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not 
     implemented for `std::rc::Rc<std::collections::HashSet<rest::Route>>`

I know that the compiler give me some help but honestly i have no idea how to do that or if i can do with some easy solution. After a large search in google i found a solution in RefCell, but is not so much clear

Thanks in advance for your help

like image 481
Andrea Mucci Avatar asked Oct 28 '19 23:10

Andrea Mucci


1 Answers

You cannot borrow a reference-counting pointer as mutable; this is because one of the guarantees it provides is only possible if the structure is read-only.

You can, however, get around it, but it will require some signature changes.

Enter interior mutability

Interior mutability is a concept you may know from other programming languages in the form of mutexes, atomics and synchronization primitives. In practice, those structures allow you to temporarily guarantee that you are the only accessor of a given variable.

In Rust, this is particularly good, as it allows us to extract a mutable reference to an interior member from a structure that only requires immutable references to itself to function. Perfect to fit in Rc.

Depending on what you need for your needs, you will find the Cell and RefCell structures to be exactly what you need for this. These are not thread-safe, but then again, neither is Rc so it's not exactly a deal-breaker.

In practice, it works very simply:

let data = Rc::new(RefCell::new(true));
{
  let mut reference = data.borrow_mut();
  *reference = false;
}
println!("{:?}", data);

playground

(If you ever want the threaded versions, Arc replaces Rc and Mutex or RwLock replaces Cell/RefCell)

like image 70
Sébastien Renauld Avatar answered Oct 01 '22 02:10

Sébastien Renauld