I suspect there's too much being generic and indirection generally. They are probably there for reasons that aren't clear from the example, but it certainly makes it hard to reason about, I keep having to bounce back up to the definitions. So for the sake of exploring, I took your playground and iteratively simplified it:
pub type LockableArc<T: ?Sized> = Arc<Mutex<Option<T>>>;
pub struct MutexGuardOptionRef<'a, T: ?Sized> {
pub mutex_guard: MutexGuard<'a, Option<Box<T>>>,
}
I just replaced the alias everywhere. The latter as presented is just a newtype; maybe you need to implement things on it, but to simply things, I replaced every MutextGuardOptionRef<_>
with a MutexGuard<'Option<Box<_>>>
as well.
After those replacements we have this:
pub trait LockableOption<T: ?Sized>: Send {
fn lock(&self) -> LockResult<MutexGuard<Option<Box<T>>>>;
}
impl<T: ?Sized + Send> LockableOption<T> for Arc<Mutex<Option<Box<T>>>> {
fn lock(&self) -> LockResult<MutexGuard<Option<Box<T>>>> {
unimplemented!()
}
}
There's only one sensible implementation though, which is (**self).lock()
. Or if you renamed the method to avoid the name conflict, just self.lock()
. At this point the trait seems superfluous as well, so I removed it.
Here's what was left. There's still a problem with finding the correct implementation. You say there should only be one run function per trait, I take this to mean that for any type T: A
, there should only be one implementation of Runnable
. This implies we should actually be writing the implementation like this:
-impl Runnable<dyn A> for S
+impl<T: A> Runnable<dyn A> for T
{
fn run(s: Arc<Mutex<Option<Box<dyn A>>>>) -> Result<(), ()> {
And this again highlights the same issue as before, of course we need to say which implementation, given this blanket rule. But if T
always must be tied to dyn A
like this, why is the dyn A
a type parameter to Runnable
? We explicitly don't want this ability to mix and match that the type parameter is allowing.
If we remove the type parameter:
-pub trait Runnable<T: ?Sized> {
- fn run(s: Arc<Mutex<Option<Box<T>>>>) -> Result<(), ()>;
+pub trait Runnable {
+ fn run(s: Arc<Mutex<Option<Box<Self>>>>) -> Result<(), ()>;
}
// ...
-impl<T: A> Runnable<dyn A> for T
+impl Runnable for dyn A
Everything compiles without fully qualified paths. If you don't need different run
behavior per dyn Trait
, you could even do this:
-impl Runnable for dyn A {
- fn run(s: Arc<Mutex<Option<Box<dyn A>>>>) -> Result<(), ()> {
+impl<T: ?Sized + Send + 'static> Runnable for T {
+ fn run(s: Arc<Mutex<Option<Box<T>>>>) -> Result<(), ()> {
OK, I think the takeaway is "see if removing the type parameter of Runnable
can satisfy your use case." I may well have removed too much indirection for your use case to work. Maybe you could start here and build back up to it.
Here's another variation where Runnable::run
takes self
, if that's a better fit.
You asked a specific question about boxes and clones, which disappeared somewhere among the simplifications. In your link, it's complaining about
doesn't satisfy `dyn LockableOption<(dyn A + 'static)>: Clone`
And if you try to make Clone
a supertrait of LockableOption
, you get
9 | pub trait LockableOption<T: ?Sized>: Clone + Send {
| -------------- ^^^^^ ...because it requires `Self: Sized`
| |
| this trait cannot be made into an object...
I still don't understand why this needs to be a trait, given the signatures, but let's say it does need to. First, see if you need the type parameter too, it may cause similar problems as above. Next, I haven't used it and it's not available in the playground, but There's A Crate For That™. I definitely trust the crate owner.
But also, instead of using dyn LockableOption
, you could be generic over some U: LockableOption
. Then you won't need to clone a Box<dyn LockableOption>
at all.