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

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

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


person Peter Jankuliak    schedule 08.04.2015    source источник


Ответы (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;
    })
});

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

person Shepmaster    schedule 08.04.2015
comment
Да, спасибо за правки. Действительно, к сожалению, я не могу использовать это решение, так как мне нужно использовать объект routing в основном потоке, прежде чем отправлять его в другой поток. Ну, всегда есть обходные пути (например, сделать то, что нужно сделать внутри другого потока, а затем отправить результат обратно через mpsc::channel), но меня больше всего интересует решение этой конкретной проблемы. - person Peter Jankuliak; 09.04.2015
comment
Один из способов, которым я думал, что это можно решить, заключался в том, что я поместил фасад и узел в один блок, а затем просто переместил блок. Но моего ржавчины для этого не хватило :). Однако я понимаю, что то, как я хочу добиться этого, может оказаться невозможным или просто не является ржавым способом ведения дел. - person Peter Jankuliak; 09.04.2015
comment
@PeterJankuliak, вам вообще не нужен Box, но после того, как я выйду из автобуса, будет предложено переместить одно в другое. ^_^ - person Shepmaster; 09.04.2015
comment
Мне больше всего нравится последний вариант, так как он не изменяет структуры, большое спасибо. - person Peter Jankuliak; 09.04.2015
comment
Привет, thread::scoped ушел, ответ сегодня несколько вводит в заблуждение. Остались ли мы с crossbeam и пулом потоков с ограниченной областью действия, как рекомендовано здесь title="передача ссылки на переменную стека в поток с областью действия">stackoverflow.com/questions/32750829/ ? Спасибо - person Rbjz; 25.05.2017