Передача двух объектов, где один содержит ссылку на другой, в поток

У меня есть два объекта, где второй требует первого, чтобы пережить его, потому что он содержит ссылку на первый. Мне нужно перенести их обоих в поток, но компилятор жалуется, что первый не живет достаточно долго. Вот код:

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 Объект перемещен в поток, ссылка больше не будет действительной. Но я не смог найти работающего решения этой проблемы, предполагая, что я хотел бы сохранить структуры нетронутыми.

Я также задавал этот вопрос на форумах Rust

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;
    })
});

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

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