Нужно ли использовать привязку let для создания более долгоживущего значения? - PullRequest
2 голосов
/ 21 января 2020

Я совсем недавно начал изучать Rust, и, работая над тестовой программой, я написал этот метод:

pub fn add_transition(&mut self, start_state: u32, end_state: u32) -> Result<bool, std::io::Error> {
    let mut m: Vec<Page>;
    let pages: &mut Vec<Page> = match self.page_cache.get_mut(&start_state) {
        Some(p) => p,
        None    => {
            m = self.index.get_pages(start_state, &self.file)?;
            &mut m
        }
    };

    // omitted code that mutates pages 
    // ...

    Ok(true)
}

он работает, как и ожидалось, но я не уверен насчет m переменная. Если я удаляю его, код выглядит более элегантно:

pub fn add_transition(&mut self, start_state: u32, end_state: u32) -> Result<bool, std::io::Error> {
    let pages: &mut Vec<Page> = match self.page_cache.get_mut(&start_state) {
        Some(p) => p,
        None    => &mut self.index.get_pages(start_state, &self.file)?
    };

    // omitted code that mutates pages
    // ...

    Ok(true)
}

, но я получаю:

error[E0716]: temporary value dropped while borrowed
  --> src\module1\mod.rs:28:29
   |
26 |           let pages: &mut Vec<Page> = match self.page_cache.get_mut(&start_state) {
   |  _____________________________________-
27 | |             Some(p) => p,
28 | |             None    => &mut self.index.get_pages(start_state, &self.file)?
   | |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
   | |                             |                                            |
   | |                             |                                            temporary value is freed at the end of this statement
   | |                             creates a temporary which is freed while still in use
29 | |         };
   | |_________- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

Я полностью понимаю ошибку, которая привела меня к работающему фрагменту, но я ' Мне интересно, есть ли более элегантный и / или идиоматический c способ написания этого кода. Я объявляю m в начале функции, только чтобы предотвратить преждевременное освобождение временной переменной. Есть ли способ сообщить компилятору, что время жизни возвращаемого значения self.index.get_pages должно быть целой add_transition функцией?

Дополнительные сведения:

  • Page относительно большая структура, поэтому я бы предпочел не реализовывать черту Copy и не клонировать ее.
  • page_cache имеет тип HashMap<u32, Vec<Page>>
  • self.index.get_pages относительно медленно и я использую page_cache для кэширования результатов
  • Тип возврата self.index.get_pages равен Result<Vec<Page>, std::io::Error>

Ответы [ 2 ]

4 голосов
/ 21 января 2020

Это нормально, ваш «чистый» код в основном сводится к следующему:

let y = {
    let x = 42;
    &x
};

Здесь должно быть очевидно, что вы не можете вернуть ссылку на x, поскольку x упал в конце блока. Эти правила не изменяются при работе с временными значениями: self.index.get_pages(start_state, &self.file)? создает временное значение, которое отбрасывается в конце блока (строка 29), и поэтому вы не можете вернуть ссылку на него.

Обходной путь через m теперь перемещает этот временный объект в m, связывающий один блок вверх, который будет жить достаточно долго, чтобы pages мог работать с ним.

Теперь для альтернатив, я думаю, page_cache - это HashMap? Тогда вы можете сделать что-то вроде let pages = self.page_cache.entry(start_state).or_insert_with(||self.index.get_pages(...))?;. Единственная проблема с этим подходом состоит в том, что get_pages возвращает результат, в то время как текущий кэш хранит Vec<Page> (только ветвь Ok). Вместо этого вы можете адаптировать кэш для хранения Result, что, на мой взгляд, семантически лучше, поскольку вы хотите кэшировать результаты этого вызова функции, так почему бы не сделать это для Err? Но если у вас есть веская причина не кэшировать Err, подход, который вы используете, должен работать просто отлично.

0 голосов
/ 24 января 2020

Ваш, вероятно, самый эффективный способ, но в теории это необязательно, и один может быть более элегантным.

Другой способ сделать это - использовать объект черты в этом случае - иметь переменную типа dyn DerefMut<Vec<Page>>. Это в основном означает, что эта переменная может содержать любой тип, который реализует черту DerefMut<Vec<Page>>>, два типа, которые делают это &mut Vec<Page> и Vec<Page>, в этом случае переменная может содержать любой из них, но на содержимое можно ссылаться только через DerefMut.

Таким образом, следующий код работает в качестве иллюстрации:

struct Foo {
    inner : Option<Vec<i32>>,
}

impl Foo {
    fn new () -> Self {
        Foo { inner : None }
    }
    fn init (&mut self) {
        self.inner = Some(Vec::new())
    }
    fn get_mut_ref (&mut self) -> Option<&mut Vec<i32>> {
        self.inner.as_mut()
    }
}

fn main () {
    let mut foo : Foo = Foo::new();
    let mut m   : Box<dyn AsMut<Vec<i32>>> = match foo.get_mut_ref() {
        Some(r) => Box::new(r),
        None    => Box::new(vec![1,2,3]),
    };
        m.as_mut().as_mut().push(4);
}

Ключом здесь является тип Box<dyn AsMut<Vec<i32>>; это означает, что это может быть ящик, который содержит любой тип, так что тип реализует AsMut<Vec<i32>>, потому что он в штучной упаковке, нам также нужно .as_mut().as_mut(), чтобы получить из него фактический &mut <Vec<i32>>.

Потому что разные типы могут иметь разные размеры; они также не могут быть размещены в стеке, поэтому они должны быть позади некоторого указателя, поэтому обычно выбирается Box, и в этом случае необходимо, чтобы перед обычным указателем, sans , принадлежащим его pointee проблемы, аналогичные тем, с которыми вы сталкиваетесь.

Можно утверждать, что этот код более элегантный, но ваш, безусловно, более эффективен и не требует дальнейшего выделения кучи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...