Hi everyone,
I am seeking some advice on a trait I am designing.
So I work with embedded systems, and some of our services need to identify the device they are running on, specifically its serial number, the OS image/version, etc. For that I have the following trait, that users can implement depending on their device:
trait Identify {
type Error;
fn get_os_name() -> Result<String, Self::Error>;
fn get_os_version() -> Result<String, Self::Error>;
// and so on
}
One example of use cases is with an OS upgrade version checker:
fn is_upgrade_allowed(id: &impl Identify) -> bool {
let version = match id.get_os_version() {
Ok(v) => v,
Err(e) => // print or log error somewhere and return false, or return an other error (with a Result<bool, ...> return type)
}
// do some stuff with version and return true if it verifies some conditions
}
The issue I have with my design is that it forces the user to use the same error type for all these functions. This might actually be good for consistency, but another problem is that some users could implement such functions in an Infallible way, which this trait can't describe (other than setting Error
to Infallible
but that only works if all functions are infallible).
I have thought of having a trait for each function, and then a super-trait which regroups them all, but I find that too much decoupling and it doesn't really show that all these functions are part of the same feature set.
There is also the solution of having an associated type per function, but that seems really ugly.
So I want your feedback, is there any way to solve this issue? Is my trait design bad? Or is it acceptable since anyway when using something that implements Identify
we have no clue on the actual implementation and thus we need to handle possible errors?
Thanks