disclaimer im a bit of a rust noob and very much feeling like i'm fighting against the compiler here, so the chance that i'm just doing something very wrong is like, certain.
I'm trying to write what is basically a package manager for some tools. currently, the way i've been trying to do it is to have a trait, Package, and implement methods/associated functions on it. here's what i've got so far:
pub struct Machine {
// Store and retrieve from manifest.toml upon save and load
pub installed_packages: HashMap<String, Box<dyn Package>>,
}
#[derive(EnumIter, Serialize, Deserialize)]
pub enum PackageType {
PackageOne,
ConflictingPackage,
}
impl PackageType {
// I dont know how I feel about returning an option here instead of a Result<Option> here.
// This function is for if installed_packages is not populated for some reason - we need to
// make best efforts to populate it.
fn try_discover(&self, config: &Config) -> Option<impl Package> {
match self {
Self::PackageOne => {
let dir = &config.package_paths.package_one;
todo!();
}
&_ => {
todo!();
}
}
}
}
impl Machine {
pub fn has<T: Package>(&self, package: &T) -> bool {
self.installed_packages.contains_key(package.name())
}
pub fn install<T: Package>(&mut self, package: &T) -> Result<()> {
if self.has(package) {
bail!("{} is already installed.", package.name());
};
package.install(self)
}
pub fn try_discover_packages(&mut self, config: &Config) -> Result<()> {
for package_type in PackageType::iter() {
let package_opt = package_type.try_discover(config);
if let Some(package) = package_opt {
self.installed_packages
.insert(package.name().to_string(), Box::new(package));
}
}
Ok(())
}
}
pub trait Package {
fn name(&self) -> &str;
fn version(&self) -> Version;
fn install(&self, machine: &mut Machine) -> Result<()>;
fn conflicts() -> Vec<PackageType>;
fn kind() -> PackageType;
}
#[derive(Serialize, Deserialize)]
pub struct PackageOne {
version: Version,
}
impl Package for PackageOne {
fn name(&self) -> &str {
"packageone"
}
fn version(&self) -> Version {
self.version.to_owned()
}
fn kind(&self) -> PackageType {
PackageType::PackageOne
}
fn install(&self, machine: &mut Machine) -> Result<()> {
todo!();
}
fn conflicts(&self) -> Vec<PackageType> {
vec![PackageType::ConflictingPackage]
}
}```
the list of packages is going to be loaded/saved from/to a manifesto.toml file. this has ended up being the biggest roadblocking step, as it seems like i'll need serialization/deserialization capabilities on my Package trait for that, and these traits don't seem to be compatible with object safety.
so i guess the question is, is there a way to get what i want out of this current structure? and if not, what's the alternative?
in particular, i'm a big fan of this bit of code:
```rust
impl Machine {
pub fn install<T: Package>(&mut self, package: &T) -> Result<()> {
if self.has(package) {
bail!("{} is already installed.", package.name());
};
package.install(self)
}
}
pub trait Package {
fn install(&self, machine: &mut Machine) -> Result<()>;
}