Как добавить ссылки на контейнер, когда заимствованные значения создаются после контейнера?

По причинам, связанным с организацией кода, мне нужно, чтобы компилятор принял следующий (упрощенный) код:

fn f() {
    let mut vec = Vec::new();
    let a = 0;
    vec.push(&a);
    let b = 0;
    vec.push(&b);
    // Use `vec`
}

Компилятор жалуется

error: `a` does not live long enough
 --> src/main.rs:8:1
  |
4 |     vec.push(&a);
  |               - borrow occurs here
...
8 | }
  | ^ `a` dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created

error: `b` does not live long enough
 --> src/main.rs:8:1
  |
6 |     vec.push(&b);
  |               - borrow occurs here
7 |     // Use `vec`
8 | }
  | ^ `b` dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created

Тем не менее, мне трудно убедить компилятор удалить вектор перед ссылками на переменные. vec.clear() не работает, и не работает drop(vec), mem::transmute() тоже не работает (чтобы заставить vec быть 'static).

Единственное решение, которое я нашел, было преобразовать ссылку в &'static _, Есть ли другой путь? Можно ли даже скомпилировать это в безопасном Rust?

1 ответ

Решение

Можно ли даже скомпилировать это в безопасном Rust?

Нет. То, что вы пытаетесь сделать, в общем случае небезопасно.

Коллекция содержит ссылку на переменную, которая будет удалена до удаления самой коллекции. Это означает, что деструктор коллекции имеет доступ к ссылкам, которые больше не действительны. Деструктор может выбрать разыменование одного из этих значений, нарушая гарантии безопасности памяти Rust.

примечание: значения в области видимости отбрасываются в обратном порядке их создания

Как говорит вам компилятор, вам нужно изменить порядок кода. Вы на самом деле не сказали, какие ограничения существуют по "причинам, связанным с организацией кода", но прямое решение таково:

fn f() {
    let a = 0;
    let b = 0;
    let mut vec = Vec::new();
    vec.push(&a);
    vec.push(&b);
}

Менее очевидный:

fn f() {
    let a;
    let b;

    let mut vec = Vec::new();
    a = 0;
    vec.push(&a);
    b = 0;
    vec.push(&b);
}

При этом, как только нелексические времена жизни будут включены, ваш оригинальный код будет работать! Средство проверки заимствований становится более детализированным о том, как долго нужно жить стоимости.

Но подожди; Я только что сказал, что коллекция может получить доступ к недопустимой памяти, если значение внутри нее будет удалено перед коллекцией, и теперь компилятор позволяет этому случиться? Что дает?

Это потому, что стандартная библиотека тянет нас на хитрость. Коллекции как Vec или же HashSet гарантировать, что они не имеют доступа к своим общим параметрам в деструкторе. Они сообщают об этом компилятору, используя нестабильный#[may_dangle] особенность.

Смотрите также:

Другие вопросы по тегам