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_version(&self, package: &DP::P, ranges: &DP::VS) -> Result<Option<DP::V>, DP::Err> { ... } 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
.