Фильтрация hash_map::Iter

Привет полумертвый форум.

Разгребаю я тут старую ошибку в ZoC и возникла у меня вот такая проблема:

use std::collections::HashMap;

#[derive(Debug)]
struct Object(i32);

trait State {
    fn objects(&self) -> &HashMap<i32, Object>;
}

struct FullState {
    objects: HashMap<i32, Object>,
}

impl FullState {
    fn new() -> FullState {
        let mut objects = HashMap::new();
        objects.insert(0, Object(100));
        objects.insert(1, Object(101));
        objects.insert(2, Object(102));
        objects.insert(3, Object(103));
        FullState {
            objects: objects,
        }
    }
}

impl State for FullState {
    fn objects(&self) -> &HashMap<i32, Object> {
        &self.objects
    }
}

struct TmpPartialState<'a> {
    full_state: &'a FullState,
}

impl<'a> TmpPartialState<'a> {
    fn new(full_state: &'a FullState) -> TmpPartialState<'a> {
        TmpPartialState {
            full_state: full_state,
        }
    }
}

impl<'a> State for TmpPartialState<'a> {
    fn objects(&self) -> &HashMap<i32, Object> {
        // Хочется, что бы эта функция возвращала не все объекты из полного
        // состояния, а, допустим, только с нечетными идентификаторами.
        // 
        // Типажа State можно менять, лишь бы этот метод в итоге был дешевым,
        // принимал &self и возвращал что-то, что сразу можно скормить
        // в for цикл.
        //
        self.full_state.objects()
    }
}

fn main() {
    let full_state = FullState::new();
    for (id, object) in full_state.objects() {
        println!("{:?} -> {:?}", id, object);
    }
    println!("---");
    let partial_state = TmpPartialState::new(&full_state);
    for (id, object) in partial_state.objects() {
        println!("{:?} -> {:?}", id, object);
    }
}

(https://play.rust-lang.org/?gist=74b077340a3be2665304acf29581477c)

И я пока додумался только до довольно громоздкого решения с реализацией своих собственных итераторов:

use std::collections::hash_map::{self, HashMap};

#[derive(Debug)]
struct Object(i32);

trait State<'a> {
    type It: Iterator;

    fn objects(&'a self) -> Self::It;
}

struct FullState {
    objects: HashMap<i32, Object>,
}

impl FullState {
    fn new() -> FullState {
        let mut objects = HashMap::new();
        objects.insert(0, Object(100));
        objects.insert(1, Object(101));
        objects.insert(2, Object(102));
        objects.insert(3, Object(103));
        FullState {
            objects: objects,
        }
    }
}

impl<'a> State<'a> for FullState {
    type It = hash_map::Iter<'a, i32, Object>;

    fn objects(&'a self) -> Self::It {
        self.objects.iter()
    }
}

struct TmpPartialState<'a> {
    full_state: &'a FullState,
}

impl<'a> TmpPartialState<'a> {
    fn new(full_state: &'a FullState) -> TmpPartialState<'a> {
        TmpPartialState {
            full_state: full_state,
        }
    }
}

struct StateIter<'a> {
    hashmap_iter: hash_map::Iter<'a, i32, Object>,
}

impl<'a> Iterator for StateIter<'a> {
    type Item = (&'a i32, &'a Object);

    fn next(&mut self) -> Option<Self::Item> {
        while let Some(pair) = self.hashmap_iter.next() {
            let (id, _) = pair;
            if id % 2 == 0 {
                return Some(pair);
            }
        }
        None
    }
}

impl<'a> State<'a> for TmpPartialState<'a> {
    type It = StateIter<'a>;

    fn objects(&self) -> Self::It {
        StateIter {
            hashmap_iter: self.full_state.objects(),
        }
    }
}

fn main() {
    let full_state = FullState::new();
    for (id, object) in full_state.objects() {
        println!("{:?} -> {:?}", id, object);
    }
    println!("---");
    let partial_state = TmpPartialState::new(&full_state);
    for (id, object) in partial_state.objects() {
        println!("{:?} -> {:?}", id, object);
    }
}

(https://play.rust-lang.org/?gist=5b0140649e250e6daa6004e97272fa33)

Может я какое-то более простое решение упускаю из виду? Нет ни у кого идей?

Гм, у меня еще сложность:

Я не хочу что бы у типажа был параметр <'a>. Оно там нужно чисто для type It = hash_map::Iter<'a, i32, Object>; в одной из реализаций.

Попробовал через фантомов перенести этот параметр в саму структуру, для которой типаж реализуется:

https://play.rust-lang.org/?gist=e46b32b6d48eface077c462a9ce6e6aa

Не работает, есть чувство что я делаю какую-то дичь :frowning:

Насчет второй сложности - так будет работать если FullState будет содержать ссылку на HashMap а не владеть им. Метод iter() берет НashMap по ссылке из которой он и выводит время жизни элементов итератора. Если FullState владеет хэшмапом, то время жизни элементов никак не соотносится с временем жизни хэшмапа (время жизни сохраненное в PhantomData не обязательно будет соответствовать вр.жизни хэшмапа).

В принципе можно одурачить компилятор:

impl<'a> State for FullState<'a> {
    type It = hash_map::Iter<'a, i32, Object>;

    fn objects(&self) -> Self::It {
        unsafe{std::mem::transmute(self.objects.iter())}
    }
}

Но я не уверен, что это всегда будет корректно работать, так что использовать не советую. Взял отсюда http://stackoverflow.com/a/39442814/5138648

Еще по второй сложности - можно изменить функцию типажа State чтобы она брала self по значению, а не по ссылке. А имплементацию делать для ссылки &'a FullState<'a>.

https://play.rust-lang.org/?gist=ccea16a0482378d3a47a6a439e11e15a&version=stable&backtrace=0

1 лайк

Вопрос: чем плохо вермя жизни у типажа? Имхо, обходные реализации выглядят неоправданно сложней.

impl<'a> State<'a> for FullState<'a> {
    type It = hash_map::Iter<'a, i32, Object>;

    fn objects(&'a self) -> Self::It {
        self.objects.iter()
    }
}

https://play.rust-lang.org/?gist=0aae6d1137df9c521b24927e52bafe1a

Вот черт, не подумал. Помогло, даже без всяких фантомов и лишних ВЖ в типаже. Спасибо! :smiley:

Вопрос: чем плохо вермя жизни у типажа?

Как минимум, у меня есть куча свободных функций со статическим полиморфизмом по этому типажу, вроде:

fn is_obstacle<S: GameState>(state: &S, pos: MapPos) -> bool {...}

Сигнатуры у многих из этих функций и так не маленькие, очень не хочется просто так в каждой заменять <S: GameState> на <'a, S: GameState<'a>>.

Хм, ловкость рук и никакого мошенничества. Круто. Это надо осознать и запомнить :slight_smile:

impl<'a> State for &'a FullState

Попробовал вставить гист выше в реальный код и напоролся еще на ряд косяков.

Во первых, если я пытаюсь добавить в свой супер-пупер типаж метод по получению ссылки на конкретный объект (а в реальном коде мне оно нужно)

 trait State {
    type Iter: Iterator;

    fn objects(self) -> Self::Iter;

    fn object(self, id: i32) -> &Object; // <- вот этот
}

то я получаю:

 error[E0106]: missing lifetime specifier
  --> <anon>:11:33
   |
11 |     fn object(self, id: i32) -> &Object;
   |                                 ^ expected lifetime parameter
   |
   = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
   = help: consider giving it an explicit bounded or 'static lifetime

И это намекает что таки не выйдет обойтись без явных ВЖ и что типаж все-таки придется реализовывать для самого типа, а не ссылки на него. Это ладно, в принципе могу я пережить эти лишние 'a повсюду, наверное.

Во вторых, я ничего не могу сделать с этим своим итератором, если нет доступа к оригинальному типу:

 fn foo<S: State>(state: &S) {
    for (id, object) in state.objects() {
        println!("{:?} -> {:?}", id, object);
    }
}
 error[E0308]: mismatched types
  --> <anon>:80:9
   |
80 |     for (id, object) in state.objects() {
   |         ^^^^^^^^^^^^ expected associated type, found tuple
   |
   = note: expected type `<<S as State>::Iter as std::iter::Iterator>::Item`
   = note:    found type `(_, _)`

(https://play.rust-lang.org/?gist=4c54a9ccec31bf19ec0128a225cf13e6)

Как-то я в растерянности. Сообщение об ошибке намекает что надо то-ли чего-то дописывать в сам типаж, то ли навешивать здоровенный where на саму функцию, но я туплю.

Вот так вроде работает:

fn foo<S,A,B>(state: S) 
where
    S: State,
    S::Iter:Iterator<Item=(A, B)>,
    A: std::fmt::Debug,
    B: std::fmt::Debug,
{
    for (id, object) in state.objects() {
        println!("{:?} -> {:?}", id, object);
    }
}

Надо прописать какой Item у итератора. И еще там ссылка была лишняя в аргументе ф-ции. Но т.к. State реализован для &FullState то вызывать надо все равно по ссылке:

foo(&full_state);

https://play.rust-lang.org/?gist=1e7c544bd32a43cf002fdc5f9fbbd8bb&version=nightly&backtrace=0

Можно отдельный типаж сделать:

trait GetObject {
    fn object(&self, id: i32) -> &Object;
}

Но если он должен зависеть от State то от лайфтаймов избавиться не выйдет:

trait GetObject<'s> where &'s Self:State+'s {
    fn object(&'s self, id: i32) -> &'s Object;
}

Пока ближе всего к моим хотелкам вот что приблизилось:

https://play.rust-lang.org/?gist=8a54b411ccb0b07825be885db447331c

upd:

даже вот так, что бы совсем близко к коду ZoC:

https://play.rust-lang.org/?gist=b5cf2366785848bb02b304799979cdfd

Да, занятно.
Проблема с типажом State была в том что его функция objects() возвращала некий обобщенный тип ограниченный Iterator-ом и время жизни этого обобщенного типа не удавалось связать с вж &self без ввода вж в типаж State.
Теперь State::objects() возвращает конкретный тип ObjectIter вж которого легко связывается с &self. Хотя ObjectIter и имеет параметр - другой обобщенный тип, вж этого другого обобщенного типа не требуется связывать с &self типажа State.
:dizzy: :dizzy_face: :dizzy:

Is external iteration acceptable? In which case it would be solved trivially:

trait State {
    fn per_object<F: FnMut(&Object)>(&self, fun :F);
}

Нет, итерация через замыкания для моего случая тже не очень подходит. :frowning:

В итоге решил все максимально упростить в коде и вообще по-другому сделать: https://github.com/ozkriff/zoc/issues/255