Module users::cache [−][src]
A cache for users and groups provided by the OS.
Because the users table changes so infrequently, it’s common for
short-running programs to cache the results instead of getting the most
up-to-date entries every time. The UsersCache
type helps with this, providing methods that have the same name as the
others in this crate, only they store the results.
Example
use std::sync::Arc; use users::{Users, UsersCache}; let mut cache = UsersCache::new(); let user = cache.get_user_by_uid(502).expect("User not found"); let same_user = cache.get_user_by_uid(502).unwrap(); // The two returned values point to the same User assert!(Arc::ptr_eq(&user, &same_user));
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 HashMap
s that hold the result of user
and group lookups. Rust provides mutability in two ways:
- Have its methods take
&mut self
, instead of&self
, allowing the internal maps to be mutated (“inherited mutability”) - 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!
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
RefCell
s 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:
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. |