Предаване на два обекта, където единият съдържа препратка към друг, в нишка

Имам два обекта, при които вторият изисква първият да го надживее, защото съдържа препратка към първия. Трябва да ги преместя и двата в нишка, но компилаторът се оплаква, че първият не живее достатъчно дълго. Ето кода:

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

Playground

И грешката:

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
Един от начините, по които смятах, че това може да се реши, беше, ако сложа и фасадата, и възела в една кутия и след това просто преместя кутията. Но моят rust-fu не беше достатъчно добър за това :). Разбирам обаче, че начинът, по който искам да постигна това, може да не е възможен или просто не е ръждивият начин за правене на нещата. - person Peter Jankuliak; 09.04.2015
comment
@PeterJankuliak изобщо не трябва да имаш нужда от Box, но преместването на едно в друго щеше да бъде предложение, след като слязох от автобуса. ^_^ - person Shepmaster; 09.04.2015
comment
Последната опция ми харесва най-много, тъй като не променя структурите, много благодаря. - person Peter Jankuliak; 09.04.2015
comment
Здравей, тъй като thread::scoped го няма, днес отговорът е малко подвеждащ. Оставаме ли с напречна греда и пул от нишки с обхват, както се препоръчва тук stackoverflow.com/questions/32750829/ ? Благодаря - person Rbjz; 25.05.2017