0

Suppose I have an object video_source: Option<Arc<Mutex<Box<dyn GetVideo>>>> and I pass it to a thread:

std::thread::spawn(||{
    loop {
        if let Some(video_source) = video_source {
            let video_frame = video_source.lock().unwrap().get();
        }
    }
})

where

trait GetVideo {
    fn get() -> Vec<u8>
}

What if I want to change the video source on the fly? Well, I'd do this on another thread:

video_frame.unwrap().lock().unwrap() = Box::new(other_source);

I want to make this idea more generic. I want a type that permits such thing. Here's my sketch:

use std::sync::{Arc, Mutex};

pub type OnTheFlyInner<T> = Box<T + Send + Sync>;
pub type OnTheFly<T> = Arc<Mutex<OnTheFlyInner<T>>>;

//I'd like this to be a method of `OnTheFly`
pub fn on_the_fly_substitute(on_the_fly: &mut Option<OnTheFly>, substitute_by: Option<OnTheFlyInner>) {
    if let Some(substitute_by) = substitute_by {
        if let Some(on_the_fly) = on_the_fly {
            *on_the_fly.lock().unwrap() = substitute_by;
        }
    } else {
        on_the_fly.take();
    }
}

However, I cannot make something generic over T where T is a trait, it should be a type.

Any ideas?


Bounty

This is solved by @user4815162342. But what if I want to make one OnTheFly object point to the same thing as the other one?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Guerlando OCs
  • 1,886
  • 9
  • 61
  • 150
  • Why not use `struct`: `struct OnTheFly { inner: OnTheFlyInner }`. Then you could implement methods on that type the usual way, with an `impl` block. – user4815162342 Jun 25 '21 at 05:31
  • @user4815162342 Thanks, I've come with this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fb2e218a87ad2a0822eac086a9abf69f. Replacing is very easy, but in order to mutate the inner value, or simply call a method on it, I have to do `*(oo.lock().unwrap().as_deref_mut().unwrap())`. As you see, I tried to make a method that locks and returns a `&mut` to the inner box, (`pub fn inner(&mut self) -> Option<&mut T> `) but it's commented because it gives a lifetime error. Do you see any way to make this work? – Guerlando OCs Jun 25 '21 at 22:43
  • To get rid of one `unwrap()`, move it to `OnTheFly::lock()`, [like this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a401e19840887a88c0a91b1f35857f45). You can't smuggle `&mut T` out of `lock()`, that kind of thing is what mutex is designed to prevent. What you need is to _project_ out of a mutex, i.e. return a `MutexGuard`-like object to part of the locked mutex. `parking_lot::Mutex` supports it, leading to [this implementation](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=feb206aebfc07f5122113340b78a3bcb), which looks like what you want. – user4815162342 Jun 26 '21 at 07:19
  • @user4815162342 thanks. There's still one problem though, what if I want to make one `on_the_fly` point to the same object hold by another? Since we're using `Box`, I don't know what to do. I thought of exchanging `Box` by `RefCell`, but `RefCell` cannot hold `dyn Trait`, which is crucial here. `Arc` can but does not have mutable interior. `Arc>` should work but involves an unncesessary second lock. Do you have something in mind? – Guerlando OCs Jun 27 '21 at 04:32
  • @user4815162342 I opened a bounty, hope you can help me, thank you very much! – Guerlando OCs Jun 27 '21 at 04:33
  • Glad to help, I'll post a proper answer later. Regarding making one `on_the_fly` point to the object held by another, don't you get that just by cloning the `OnTheFly` (after adding `#[derive(Clone)]` to the struct)? `OnTheFly` holds an `Arc`, and cloning an `Arc` creates a new `Arc` pointing to the same underlying object, which is what you're after. (Being able to do this is the whole point of using `Arc` in the first place, otherwise you could just use a `Box`.) – user4815162342 Jun 27 '21 at 10:29
  • @user4815162342 indeed, but after some time, I realized that then I cannot make this struct not point to that object anymore. If I try to make it point to another, it simply will alter the 'global' one. Of course I could simply swap the member of the struct by another `OnTheFly`, but the internal threads of this struct will not follow this swap, they will still have an `Arc` that points to the old one – Guerlando OCs Jun 27 '21 at 20:47
  • This is why I was reluctant to write an answer - I have no clue what the actual requirements are, and they diverge from the question with each new comment. Maybe you need another level of indirection, such as `Arc>>>`? At this point you have all the tools to implement whatever you need by combining the smart pointer and interior mutability. Good luck. – user4815162342 Jun 28 '21 at 05:46
  • @user4815162342: I've heard that any problem in CS can be solved with yet another level of indirection, but a `Mutex` inside a `Mutex` is just too much for me! – rodrigo Jun 29 '21 at 16:32
  • @rodrigo Agreed - one of the mutexes is likely unnecessary and the inner Arc ccould probably be a Box (allocation still needed to hold the trait obj), but it's hard to tell as the requirements keep shifting. The most non-trivial part is the mutex projection which is a shame to remain buried in a comment, but such is life. :) – user4815162342 Jun 29 '21 at 17:21

1 Answers1

5

First, you are correct that T cannnot be a trait like GetVideo; traits are not types. However, T can be dyn GetVideo.

Second, your aliases have generic parameters, so they should be reflected as such in the function signature:

pub fn on_the_fly_substitute<T>(on_the_fly: &mut Option<OnTheFly<T>>, substitute_by: Option<OnTheFlyInner<T>>)
                            ^^^                                 ^^^                                      ^^^

Third, your alias looks like an attempt to constrain T to be Send + Sync, but aliases cannot define additional bounds. You would instead put them on the function (with ?Sized since you want to allow trait objects):

pub fn on_the_fly_substitute<T: ?Sized>(on_the_fly: &mut Option<OnTheFly<T>>, substitute_by: Option<OnTheFlyInner<T>>)
where
    T: ?Sized + Send + Sync
{
    ...
}

Note: your function body does not require Send and Sync so these bounds should probably not be included.

Fourth, Option<Arc<Mutex<Box<dyn GetVideo>>>> is not thread safe. You'll need to constrain that the trait object is at least Send:

Option<Arc<Mutex<Box<dyn GetVideo + Send>>>>
                                  ^^^^^^

Fifth, a complete example is lacking, but you appear to be wanting multiple threads to modify the same video_source. This would likely not compile since you would need multiple threads to keep a &mut _ in order to change it.

If you want shared ownership of a value that might not exist, move the option into the Mutex and adjust your function and aliases accordingly:

video_source: Arc<Mutex<Option<Box<dyn GetVideo>>>>

Sixth, your comment "I'd like this to be a method of OnTheFly" is misguided. Aliases are just aliases, you'd need a method on the aliased Option/Arc type. Keep it as a free function, introduce an extension trait for it, or create it as a wrapper type instead of an alias if you want more fine-grained control.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Thanks, I've come with this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fb2e218a87ad2a0822eac086a9abf69f. Replacing is very easy, but in order to mutate the inner value, or simply call a method on it, I have to do `*(oo.lock().unwrap().as_deref_mut().unwrap())`. As you see, I tried to make a method that locks and returns a `&mut` to the inner box, (`pub fn inner(&mut self) -> Option<&mut T> `) but it's commented because it gives a lifetime error. Do you see any way to make this work? – Guerlando OCs Jun 25 '21 at 22:42
  • @GuerlandoOCs you cannot return an unguarded reference to the inner value of a mutex. There are workarounds [here](https://stackoverflow.com/questions/40095383/how-to-return-a-reference-to-a-sub-value-of-a-value-that-is-under-a-mutex). – kmdreko Jun 25 '21 at 22:49
  • Thanks. I've come up with this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=158c1d3a1eab2b2515279ad7855a1444, inspired on your link. What do you think? Observe that I had to make `into_inner()`. I tried to implement `Deref` directly to the `MutexGuardRef` but it wouldn't work as I'd have to return a temporary – Guerlando OCs Jun 26 '21 at 01:46
  • @GuerlandoOCs `Deref` works for me: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f2d03cbad4de43a55d290cdc29814f20) – Cryptjar Jun 30 '21 at 12:31
  • @Cryptjar indeed, but I was trying to deref to `Option<&T>` instead of `Option>`, which I think it's not possible. The double pointer is kinda ugly, I think it makes more sense to call `inner` to get an `Option<&T>` – Guerlando OCs Jun 30 '21 at 18:59