Передача двух объектов, где один содержит ссылку на другой, в поток
У меня есть два объекта, где второй требует первого, чтобы пережить его, потому что он содержит ссылку на первый. Мне нужно перенести их обоих в поток, но компилятор жалуется, что первый не живет достаточно долго. Вот код:
use std::thread;
trait Facade: Sync {
fn add(&self) -> u32;
}
struct RoutingNode<'a> {
facade: &'a (Facade + 'a),
}
impl<'a> RoutingNode<'a> {
fn new(facade: &'a Facade) -> RoutingNode<'a> {
RoutingNode { facade: facade }
}
}
fn main() {
struct MyFacade;
impl Facade for MyFacade {
fn add(&self) -> u32 {
999u32
}
}
let facade = MyFacade;
let routing = RoutingNode::new(&facade);
let t = thread::spawn(move || {
let f = facade;
let r = routing;
});
t.join();
}
И ошибка:
error: `facade` does not live long enough
--> <anon>:27:37
|
27 | let routing = RoutingNode::new(&facade);
| ^^^^^^ does not live long enough
...
35 | }
| - borrowed value only lives until here
|
= note: borrowed value must be valid for the static lifetime...
Я полагаю, что я понимаю, что говорит мне ошибка: facade
Объект перемещен в поток, ссылка больше не будет действительной. Но я не смог найти работающего решения этой проблемы, предполагая, что я хотел бы сохранить структуры нетронутыми.
1 ответ
Основная проблема заключается в том, что если у вас есть ссылка на элемент, вы не можете переместить этот элемент. Давайте посмотрим на упрощенный пример памяти:
let a = Struct1; // the memory for Struct1 is on the stack at 0x1000
let b = &a; // the value of b is 0x1000
let c = a; // This moves a to c, and it now sits on the stack at 0x2000
О нет, если мы попытаемся использовать ссылку в b
(который по-прежнему указывает на 0x1000
), тогда мы получим доступ к неопределенной памяти! Это именно тот класс ошибок, который Rust помогает предотвратить - ура для Rust!
Как это исправить, зависит от вашей реальной ситуации. В вашем примере я бы предложил переместить facade
в поток, затем создайте RoutingNode
по ссылке в стеке потока:
let facade = MyFacade;
let t = thread::spawn(move || {
let f = facade;
let r = RoutingNode::new(&f);
});
Это часть ответа, где люди обычно говорят "но этот демонстрационный код не то, что делает мой настоящий код", поэтому я с нетерпением жду дополнительной сложности!
к сожалению, я не могу использовать это решение, так как мне нужно использовать объект маршрутизации в основном потоке перед отправкой его в другой поток
Я вижу несколько вариантов здесь. Самое простое - это чтобы обернутый объект стал владельцем обернутого объекта, а не просто имел ссылку:
use std::thread;
trait Facade: Sync {
fn add(&self) -> u32;
}
struct RoutingNode<F> {
facade: F,
}
impl<F> RoutingNode<F>
where
F: Facade,
{
fn new(facade: F) -> RoutingNode<F> {
RoutingNode { facade }
}
}
fn main() {
struct MyFacade;
impl Facade for MyFacade {
fn add(&self) -> u32 {
999u32
}
}
let facade = MyFacade;
let routing = RoutingNode::new(facade);
let t = thread::spawn(move || {
let r = routing;
});
t.join().expect("Unable to join");
}
Другой вариант - использовать темы с определенными областями. Это позволяет вам иметь поток, который может иметь ссылки извне замыкания, но должен быть присоединен до того, как заимствованные переменные выйдут из области видимости. Два потенциальных поставщика потоков с определенными областями:
Использование перекладины:
extern crate crossbeam;
let facade = MyFacade;
let routing = RoutingNode::new(&facade);
crossbeam::scope(|scope| {
scope.spawn(|| {
let r = routing;
})
});
Я предпочитаю первый вариант, если он имеет смысл для вашей ситуации. Мне также нравится второй вариант, так как часто потоки имеют время жизни, которое не должно быть всей программой.