Anyway to move an Arc from the enclosing function to a next closure?

I'm playing around with observables from TC39 proposal and wrote this little test:

let list = Arc::new(RwLock::new(vec![]));
let list2 = Arc::clone(&list);
Observable::<_, ()>::new(Arc::new(|observer| {
    for color in ["red", "green", "blue"] {
        observer.next(color.to_owned());
    }
    Arc::new(|| {
        // cleanup
    })
}))
    .subscribe(observer! {
        next: move |color| {
            list2.write().unwrap().push(color);
        },
    });
assert_eq!(
    *list.read().unwrap(),
    Vec::from_iter(["red", "green", "blue"])
);

Is there a way to omit this list2 for readability? I tried to capture list by cloning its reference first (via Arc::clone) and no luck.

No, I don't think you can get rid of your second variable. You must provide your closure with a variable that can be moved to the closure, without it being used afterwards somewhere else. Passing list to your closure with move semantics, even though you would immediately clone it, is not recognised by the compiler as a special case where the clone is created before the move, moving the clone rather than list. list is first moved into the closure and only cloned afterwards.

Your Observable type is somewhat a red herring I'd say. The behaviour you are experiencing can be boiled down to a minimal example like this:

use std::sync::Arc;

fn main() {
    let x = Arc::new(0);
    let y = x.clone();
    
    let _ = move || {
        // x.clone() // doesn't work unfortunately, because x gets moved before it is cloned
        y
    };
    
    println!("{x}");
}

Playground.

1 Like

Right.

I implemented it exactly as in the original proposal by looking at the sources and callbacks can come from any place and used in different ways. I had to add some trait constraints such as 'static + Send + Sync to all impls operating on observables.

I'm not sure it's a "bad" pratice, but that type can be used in a variety of ways

I'm not saying it is bad practice. All I'm saying is is that it is drawing attention away from the real problem at hand. Then again, it makes it clear that your closures require move semantics if they capture from the environment, as working with threads (which your Observable must be doing at some point) requires the 'static bound. So it may not be as much of a red herring as I initially thought while trying to make sense of your example :slightly_smiling_face:

2 Likes

You can make block expressions to shift the variable close to the closure.

    next: {
        let list2 = Arc::clone(&list);
        move |color| {
            list2.write().unwrap().push(color);
        }},
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.