Module users::cache [] [src]

A cache for users and groups provided by the OS.

Caching, multiple threads, and mutability

The UsersCache type is caught between a rock and a hard place when it comes to providing references to users and groups.

Instead of returning a fresh User struct each time, for example, it will return a reference to the version it currently has in its cache. So you can ask for User #501 twice, and you’ll get a reference to the same value both time. Its methods are idempotent -- calling one multiple times has the same effect as calling one once.

This works fine in theory, but in practice, the cache has to update its own state somehow: it contains several HashMaps that hold the result of user and group lookups. Rust provides mutability in two ways:

  1. Have its methods take &mut self, instead of &self, allowing the internal maps to be mutated (“inherited mutability”)
  2. Wrap the internal maps in a RefCell, allowing them to be modified (“interior mutability”).

Unfortunately, Rust is also very protective of references to a mutable value. In this case, switching to &mut self would only allow for one user to be read at a time!

norun let mut cache = UsersCache::empty_cache(); let uid = cache.get_current_uid(); // OK... let user = cache.get_user_by_uid(uid).unwrap() // OK... let group = cache.get_group_by_gid(user.primary_group); // No!`

When we get the user, it returns an optional reference (which we unwrap) to the user’s entry in the cache. This is a reference to something contained in a mutable value. Then, when we want to get the user’s primary group, it will return another reference to the same mutable value. This is something that Rust explicitly disallows!

The compiler wasn’t on our side with Option 1, so let’s try Option 2: changing the methods back to &self instead of &mut self, and using RefCells internally. However, Rust is smarter than this, and knows that we’re just trying the same trick as earlier. A simplified implementation of a user cache lookup would look something like this:

norun fn get_user_by_uid(&self, uid: uid_t) -> Option<&User> { let users = self.users.borrow_mut(); users.get(uid) }`

Rust won’t allow us to return a reference like this because the Ref of the RefCell just gets dropped at the end of the method, meaning that our reference does not live long enough.

So instead of doing any of that, we use Arc everywhere in order to get around all the lifetime restrictions. Returning reference-counted users and groups mean that we don’t have to worry about further uses of the cache, as the values themselves don’t count as being stored in the cache anymore. So it can be queried multiple times or go out of scope and the values it produces are not affected.

Structs

UsersCache

A producer of user and group instances that caches every result.