Caching dependencies in a DependencyProvider

A dependency provider can be reused for multiple resolutions, usually of the same package and thus asking for the same dependencies. Since dependencies are generally immutable, caching them is a valid strategy to avoid slow operations that may be needed such as fetching remote data. But caching data in a dependency provider while computing the result of get_dependencies would require &mut self instead of &self. We wanted to encode in the type that the resolve function cannot mutate on its own a dependency provider so that's why we chose &self. But if the dependency provider wants to mutate itself there is a pattern called interior mutability enabling exactly that. We will now setup a simple example of how to do that. If by the end you think the trade-off is not worth it and we should use &mut self in the method signature, please let us know with an issue in pubgrub repository.

Let DependencyCache be our cache for dependencies, with existing methods to fetch them over the network,


#![allow(unused)]
fn main() {
struct DependencyCache<P: Package, V: Version> {
    cache: Map<P, BTreeMap<V, DependencyConstraints<P, V>>>,
}

impl<P: Package, V: Version> DependencyCache<P, V> {
    /// Initialize cached dependencies.
    pub fn new() -> Self { ... }
    /// Fetch dependencies of a given package on the network and store them in the cache.
    pub fn fetch(&mut self, package: &P, version: &V) -> Result<(), Box<dyn Error>> { ... }
    /// Extract dependencies of a given package from the cache.
    pub fn get(&self, package: &P, version: &V) -> Dependencies { ... }
}
}

We can implement the DependencyProvider trait in the following way, using RefCell for interior mutability in order to cache dependencies.


#![allow(unused)]
fn main() {
pub struct CachingDependencyProvider<P: Package, V: Version> {
    cached_dependencies: RefCell<DependencyCache>,
}

impl<P: Package, V: Version> DependencyProvider<P, V> for CachingDependencyProvider<P, V> {
    fn choose_package_version<...>(...) -> ... { ... }
    fn get_dependencies(
        &self,
        package: &P,
        version: &V,
    ) -> Result<Dependencies<P, V>, Box<dyn Error>> {
        match self.cached_dependencies.get(package, version) {
            deps @ Dependencies::Kown(_) => Ok(deps),
            Dependencies::Unknown => {
                self.cached_dependencies.borrow_mut().fetch(package, version)?;
                Ok(self.cached_dependencies.get(package, version))
            }
        }
    }
}
}

An example of caching based on the OfflineDependencyProvider is available in examples/caching_dependency_provider.rs.