/// Stores multiple DataSource capable of InstanceTitle, InstanceBaseUrl and
/// RepoListUrl
#[derive(Default)]
pub struct ConfigManager {
// conceptually the actual objects
bases: Vec<Arc<RwLock<dyn DataSourceBase + Send + Sync>>>,
// conceptually just views of the above objects
titles: Vec<Option<Arc<RwLock<dyn DataSource<InstanceTitle> + Send + Sync>>>>,
urls: Vec<Option<Arc<RwLock<dyn DataSource<InstanceBaseUrl> + Send + Sync>>>>,
repolists: Vec<Option<Arc<RwLock<dyn DataSource<RepoListUrl> + Send + Sync>>>>,
durations: Vec<Duration>,
// add_source can be called after update.
valid: usize,
}
(note: no Clone) and it's used like this:
/// Adds the given combined `DataSource` to this `ConfigManager`.
pub fn add_source<T>(&mut self, source: T)
where
T: DataSource<InstanceTitle>,
T: DataSource<InstanceBaseUrl>,
T: DataSource<RepoListUrl>,
T: Send + Sync + 'static,
{
let arc = Arc::new(RwLock::new(source));
self.bases.push(arc.clone());
self.titles.push(Some(arc.clone()));
self.urls.push(Some(arc.clone()));
self.repolists.push(Some(arc));
}
// and some other code that uses &mut self, but it doesn't
// add/remove from the vecs so there's no point. it does get write
// locks to the things tho - while there are no read locks around.
and unfortunately these locks are extremely unnecessary, their only purpose is to satisfy the borrow checker (a RefCell would do fine but RefCell is not Send+Sync) and they're there because Weak is useless (even tho Weak doesn't give you access to the data and Arc can provide locking of Weaks, Arc::get_mut won't let you unless you have no Weaks around). arguably even Arc is unnecessary here but it's being used for Send+Sync.
Anyway this code is sad and unnecessarily expensive and we just wish we had a better way of doing this.
Perhaps, if you are able to modify your Base trait, to include methods which get Option<&dyn Trait> for each other trait you have, you could do away with the separate lists entirely?
You say they are just views, so perhaps it's better to just have a single list of sources and get "views" to them live, instead of precomputing (and therefore involving shared ownership)?
But we don't wanna hardcode these. The whole system is supposed to be modular and flexible and extensible and whatnot. Having the Base trait provide those would kinda defeat the purpose of having the DataSource trait...
It's hard to tell, without a deeper understanding of your architecture, but I would still consider a way to eliminate the precomputed views - there may be a way to do so without restricting the flexibility of the design.
another alternative might be an alternative Cell type, there are a few implementations floating around out there that allow you to compile-time check borrows via external handles, I found the qcell crate with a quick search:
This would only be sound if you could have Arc<Invariant<T>> (e.g. Arc<UnsafeCell<T>> except the Cell types are useless for this due to not being Send+Sync). Ah well.