Да
Вполне возможно использовать право собственности и проверку заимствований для создания собственных проверок безопасности, и это действительно очень захватывающая область исследования, которая открыта для нас.
Я бы хотел начать с уже имеющихся интересных вещей:
Типы сеансов предназначены для кодирования конечных автоматов в системе типов:
- The "state" is encoded as a type
- «Переход» кодируется как метод, использующий одно значение и создающий другое, возможно, другого типа.
- В результате: (1) переходы проверяются во время выполнения и (2) невозможно использовать старое состояние.
Есть уловки, позволяющие использовать заимствование для создания гарантированно действительного индекса для конкретной коллекции (связанных с брендингом):
- The index borrows the collection, guaranteeing the collection cannot be modified
- Индекс создается с неизменным временем жизни, которое связывает его с этим экземпляром коллекции и никаким другим
- В результате: индекс можно использовать только с этой коллекцией и без проверки границ
Перейдем к вашим примерам:
// get a reference to an edge
let edge = graph.get_random_edge()
// the next statement yields the ownership of the edge reference
// back to the graph, which can invalidate it
edge.split()
edge.next() // this will be a compile-time error as the edge is gone!
На самом деле это тривиально.
В Rust вы можете определить метод стать владельцем получателя:
impl Edge {
fn split(self) { ... }
// ^~~~ Look, no "&"
}
Как только значение потреблено, его нельзя больше использовать, и поэтому вызов next
недействителен.
Я полагаю, что вы хотите, чтобы Edge
сохраняла ссылку на график, чтобы предотвратить изменение графика, пока у вас есть выдающееся преимущество:
struct Edge<'a> {
graph: &'a Graph, // nobody modifies the graph while I live!
}
сделает свое дело.
Двигаемся дальше:
// another example
let edge1 = graph.get_random_edge()
let edge2 = graph.get_random_edge()
// this will be a compile-time error because the potentially invalid
// edge2 reference is still owned by the code and has not been
// yielded to the graph
edge1.split()
Это невозможно, как есть.
Чтобы обеспечить порядок, значения должны быть связаны друг с другом, а здесь edge1
и edge2
нет.
Простое решение - потребовать, чтобы edge1
действовал как обязательный прокси для графа:
struct Edge<'a> {
graph: &'a mut Graph, // MY PRECIOUS!
// You'll only get that graph over my dead body!
}
Затем мы реализуем геттер, чтобы временно получить доступ к графу:
impl<'a> Edge<'a> {
fn get_graph<'me>(&'me mut edge) -> &'me mut Graph;
}
И использует этот результат (названный graph2
для удобства) для получения edge2
.
Это создает цепочку обязательств:
- Никто не может коснуться
graph
, пока edge1
не умрет
- Никто не может коснуться
edge1
, пока graph2
не умрет
- Никто не может коснуться
graph2
, пока edge2
не умрет
который обеспечивает выпуск объектов в правильном порядке.
Во время компиляции.
\o/
Примечание по безопасности: важным событием сразу после выпуска Rust стал LeakPocalypse ( scoped_thread
были признаны ненадежными), что привело к тому, что Ганкро (писавший и руководивший std::collections
) написал Предварительно намазайте штаны ржавчиной, которую я рекомендую вам прочитать. Суть в том, что вы НИКОГДА не должны полагаться на деструктор, выполняемый в целях безопасности, потому что нет никакой гарантии, что это произойдет (объект может просочиться, а затем поток раскрутится из-за паники). Pre-Pooping Your Pants - это стратегия, предложенная Gankro для решения этой проблемы: перевести элемент в допустимое и безопасное (если семантически неправильное) состояние, выполнить свои действия, восстановить реальную семантику при уничтожении, и это то, что используется Drain
итератор.
person
Matthieu M.
schedule
25.01.2017