tradinggy
tradinggy

Reputation: 1251

Returning iterator from weak references for mapping and modifying values

I'm trying quite complex stuff with Rust where I need the following attributes, and am fighting the compiler.

  1. Object which itself lives from start to finish of application, however, where internal maps/vectors could be modified during application lifetime
  2. Multiple references to object that can read internal maps/vectors of an object
  3. All single threaded
  4. Multiple nested iterators which are map/modified in lazy manner to perform fast and complex calculations (see example below)

A small example, which already causes problems:

use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Weak;

pub struct Holder {
    array_ref: Weak<RefCell<Vec<isize>>>,
}

impl Holder {
    pub fn new(array_ref: Weak<RefCell<Vec<isize>>>) -> Self {
        Self { array_ref }
    }

    fn get_iterator(&self) -> impl Iterator<Item = f64> + '_ {
        self.array_ref
            .upgrade()
            .unwrap()
            .borrow()
            .iter()
            .map(|value| *value as f64 * 2.0)
    }
}

get_iterator is just one of the implementations of a trait, but even this example already does not work.

The reason for Weak/Rc is to make sure that multiple places points to object (from point (1)) and other place can modify its internals (Vec<isize>).

What is the best way to approach this situation, given that end goal is performance critical?

EDIT: Person suggested using https://doc.rust-lang.org/std/cell/struct.Ref.html#method.map

But unfortunately still can't get - if I should also change return type - or maybe the closure function is wrong here

fn get_iterator(&self) -> impl Iterator<Item=f64> + '_ {
    let x = self.array_ref.upgrade().unwrap().borrow();
    let map1 = Ref::map(x, |x| &x.iter());
    let map2 = Ref::map(map1, |iter| &iter.map(|y| *y as f64 * 2.0));
    map2
}

IDEA say it has wrong return type

 the trait `Iterator` is not implemented for `Ref<'_, Map<std::slice::Iter<'_, isize>, [closure@src/bin/main.rs:30:46: 30:65]>>`

Upvotes: 0

Views: 140

Answers (1)

cdhowie
cdhowie

Reputation: 169478

This won't work because self.array_ref.upgrade() creates a local temporary Arc value, but the Ref only borrows from it. Obviously, you can't return a value that borrows from a local.

To make this work you need a second structure to own the Arc, which can implement Iterator in this case since the produced items aren't references:

pub struct HolderIterator(Arc<RefCell<Vec<isize>>>, usize);

impl Iterator for HolderIterator {
    type Item = f64;
    fn next(&mut self) -> Option<f64> {
        let r = self.0.borrow().get(self.1)
            .map(|&y| y as f64 * 2.0);
            
        if r.is_some() {
            self.1 += 1;
        }
        
        r
    }
}

// ...

impl Holder {
    // ...

    fn get_iterator<'a>(&'a self) -> Option<impl Iterator<Item=f64>> {
        self.array_ref.upgrade().map(|rc| HolderIterator(rc, 0))
    }
}

Alternatively, if you want the iterator to also weakly-reference the value contained within, you can have it hold a Weak instead and upgrade on each next() call. There are performance implications, but this also makes it easier to have get_iterator() be able to return an iterator directly instead of an Option, and the iterator written so that a failed upgrade means the sequence has ended:

pub struct HolderIterator(Weak<RefCell<Vec<isize>>>, usize);

impl Iterator for HolderIterator {
    type Item = f64;
    fn next(&mut self) -> Option<f64> {
        let r = self.0.upgrade()?
            .borrow()
            .get(self.1)
            .map(|&y| y as f64 * 2.0);
            
        if r.is_some() {
            self.1 += 1;
        }
        
        r
    }
}

// ...

impl Holder {
    // ...

    fn get_iterator<'a>(&'a self) -> impl Iterator<Item=f64> {
        HolderIterator(Weak::clone(&self.array_ref), 0)
    }
}

This will make it so that you always get an iterator, but it's empty if the Weak is dead. The Weak can also die during iteration, at which point the sequence will abruptly end.

Upvotes: 1

Related Questions