Можно ли полностью описать время жизни Rust с помощью системы вариаций типов?

Насколько я понимаю, вариация — это конструкция системы типов, на основе которой компилятор мог бы реализовать большую часть предполагаемой семантики всей идеи всего жизненного цикла просто на чисто уровне системы типов, как показано в примере, приведенном в nomicon: assign Функция хочет получить конкретный тип T для своих двух входных аргументов, но найти его невозможно, поскольку &mut T инвариантен относительно T. Все идет нормально.

fn assign<T>(input: &mut T, val: T) {
    *input = val;
}

fn main() {
    let mut hello: &'static str = "hello";
    {
        let world = String::from("world");
        assign(&mut hello, &world);
    }
    println!("{hello}"); // use after free 😿
}

А как насчет этого фрагмента?

fn main() {
    let mut s = String::from("outer");
    let mut v = Vec::new();
    v.push(&mut s);
    {
        let mut s = String::from("inner");
        v.push(&mut s);
    }

    // code compiles iff this line is commented out
    // what does variance have to say about this `println!`?
    println!("{:?}", v);
}

Я понял, что из-за того, что &'a mut T ковариантен по отношению к 'a, Rust может выполнить разрешение типов, чтобы определить, что вектор действительно является чем-то вроде Vec<&'inner mut String>, то есть конкретный тип T в Vec<T> равен &'inner mut String, в результате чего внутренний блок компилируется, но как именно компилируется система вариантов определяет, что строка println! неуместна? Другими словами, мой вопрос заключается в том, что, насколько я понимаю, дисперсия заключается в том, что система типов выполняет разрешение типов, а это означает, что она применима, если существует несколько типов, и мы хотели бы посмотреть, можно ли их привести к одному и тому же типу, но println! не имеет другого типа для сравнения.

Или, может быть, дисперсия — это еще не вся картина, и в конечном итоге мы будем полагаться на какую-то другую семантику времени жизни, чтобы определить, является ли код валидным Rust? Вдобавок ко всему, вся идея подтипа, на которой основана дисперсия, также в конечном итоге вытекает из того, что время жизни покрывается каким-то другим временем жизни...

Кстати, что такое модель памяти и что значит сказать, что в Rust ее сейчас нет?

Вот мое понимание того, что происходит: v имеет тип Vec<&'a mut String>. 'a — это не 'inner, а какая-то другая анонимная жизнь. Метод push принимает &'a mut String. Поэтому, когда вы передаете ему &'outer mut String, вы получаете ограничение 'outer: 'a (из-за ковариации). Аналогично 'inner: 'a. Средство проверки заимствований определяет, что v должен быть активным при вызове println!, поэтому все времена жизни в его типе (здесь 'a) должны быть активными. Он также видит, что 'inner должен закончиться, прежде чем s будет отброшен. Это означает, что ограничение 'inner: 'a нарушено и вы получаете ошибку.

FZs 18.08.2024 14:53

Компилятор достаточно умен, чтобы заставить v удалиться именно тогда, когда он больше не может жить, поскольку вы используете v после того, как он больше не может жить, компилятор полностью останавливает вас. Я не думаю, что ковариант здесь имеет какое-либо значение. Я думаю, что имя USB - это просто

Stargateur 18.08.2024 14:58

Единственное значение, которое здесь имеет ковариация, — это возможность передать ссылку на push, которая не живет ровно столько же, сколько T из Vec<T>.

FZs 18.08.2024 15:04
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
3
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

На самом деле дисперсия — это один из видов ограничений: она говорит нам, может ли продолжительность жизни увеличиваться или сокращаться. Но есть и другие ограничения. Те, которые я знаю (и я считаю, что это единственные существующие), — это ограничения срока службы, ограничения времени жизни и изменяемые общие ограничения xor.

Ограничения срока действия генерируются либо потому, что они записаны как границы ('a: 'b, T: 'a), либо потому, что они необходимы для потока управления. Например. Если мы отправим ссылку &'a str на вектор Vec<&'b str>, мы узнаем 'a: 'b.

Ограничения времени жизни возникают из-за времени жизни переменных. Если переменная удаляется раньше другой, ее время жизни уменьшается, и это создает ограничение.

Изменяемые общие ограничения xor генерируются, чтобы гарантировать отсутствие перекрывающихся изменяемых ссылок.

Вернемся к вашему фрагменту. Давайте аннотируем времена жизни (используя воображаемый синтаксис. Я также проигнорировал несколько времен жизни):

fn main() {
    let mut s: 'a = String::from("outer");
    let mut v: Vec<'b> + 'c = Vec::new();
    v.push(&'d mut s);
    {
        let mut s: 'e = String::from("inner");
        v.push(&'f mut s);
    }

    &'g v; // I replaced `println!()` with this since this is equivalent for borrows.
}

Наши ограничения (обратите внимание: я не утверждаю, что именно это и делает программа проверки заимствований. Это всего лишь демонстрация):

'd: 'b, 'f: 'b // pushed into the vector
'e: 'f         // the value lifetime
'e == L6..7    // the value lifetime
'b: 'c         // with T<'a>, 'a always outlives T
'c: 'g         // because of the borrow
'g == L10      // this is where it happens

Если вы попытаетесь решить эту систему, то получите L6..7: L10, что явно неверно. Вот почему программа проверки заимствований отклоняет эту программу.

Однако если мы опустим последнюю строку, мы опустим ограничения 'c: 'g и 'g == L10, и тогда система найдет решение.

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