Насколько я понимаю, вариация — это конструкция системы типов, на основе которой компилятор мог бы реализовать большую часть предполагаемой семантики всей идеи всего жизненного цикла просто на чисто уровне системы типов, как показано в примере, приведенном в 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 удалиться именно тогда, когда он больше не может жить, поскольку вы используете v после того, как он больше не может жить, компилятор полностью останавливает вас. Я не думаю, что ковариант здесь имеет какое-либо значение. Я думаю, что имя USB - это просто
Единственное значение, которое здесь имеет ковариация, — это возможность передать ссылку на push
, которая не живет ровно столько же, сколько T
из Vec<T>
.
Сроки жизни создают ограничения. Дисперсия — это только часть этого. Средство проверки заимствований генерирует уравнение ограничений и проверяет, существует ли решение.
На самом деле дисперсия — это один из видов ограничений: она говорит нам, может ли продолжительность жизни увеличиваться или сокращаться. Но есть и другие ограничения. Те, которые я знаю (и я считаю, что это единственные существующие), — это ограничения срока службы, ограничения времени жизни и изменяемые общие ограничения 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
, и тогда система найдет решение.
Вот мое понимание того, что происходит:
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
нарушено и вы получаете ошибку.