I'm lost: Rc<Box<dyn Linkage>>

:frowning: 'm lost!
I'm learning Rust. And along that way, I need a doupble linked list. shrug I know! Don't do that! But I need to.:frowning:
This program can't do it at compile time. The setup how structs are connected and even which ones are actually used is determined by a configuration file.
I have tried for a week now to find a solution. I have read that "double linked list" article (that didn't help me).
I have read this article How not to learn Rust and got even more upset and angry.
But I'm stubborn.
So here is my take:

use std::rc::Rc;

pub type DCFloat = f64;

trait Linkage {
    fn set_left(&mut self, left: Rc<Box<dyn Linkage>>);
    fn set_right(&mut self, right: Rc<Box<dyn Linkage>>);

    fn call_left(&self);
    fn get_value(&self) -> DCFloat;
}

struct Part1 {
    my_left: Option<Rc<Box<dyn Linkage>>>,
    my_right: Option<Rc<Box<dyn Linkage>>>,
    some_int: i32,
}

impl Part1 {
    fn new() -> Self {
        return Part1 {
            my_left: None,
            my_right: None,
            some_int: 42,
        };
    }
}

impl Linkage for Part1 {
    fn set_left(&mut self, left: Rc<Box<dyn Linkage>>) {
        self.my_left = Some(left);
    }

    fn set_right(&mut self, right: Rc<Box<dyn Linkage>>) {
        self.my_right = Some(right);
    }

    fn call_left(&self) {
        if let Some(my_left) = self.my_left.as_ref() {
            let f = my_left.get_value();
            println!("Part1: got {f}");
        } else {
            println!("Part1: no left!");
        }
    }
    fn get_value(&self) -> DCFloat {
        println!("Part1: returning 1.1");
        1.1
    }
}
/// Following Part2 is the same as Part1, just the name changed.
/// Part2 will have additional functions outside of the trait. Left out here for simplicity
struct Part2 {
    my_left: Option<Rc<Box<dyn Linkage>>>,
    my_right: Option<Rc<Box<dyn Linkage>>>,
    some_float: f32,
}

impl Part2 {
    fn new() -> Self {
        return Part2 {
            my_left: None,
            my_right: None,
            some_float: 47.11,
        };
    }
}

impl Linkage for Part2 {
    fn set_left(&mut self, left: Rc<Box<dyn Linkage>>) {
        self.my_left = Some(left);
    }

    fn set_right(&mut self, right: Rc<Box<dyn Linkage>>) {
        self.my_right = Some(right);
    }

    fn call_left(&self) {
        if let Some(my_left) = self.my_left.as_ref() {
            let f = my_left.get_value();
            println!("Part2: got {f}");
        } else {
            println!("Part2: no left!");
        }
    }
    fn get_value(&self) -> DCFloat {
        println!("Part2: returning 2.2");
        2.2
    }
}

/* ====================================================================== */
fn main() {
    let mut p1 = Rc::new(Box::new(Part1::new()));
    let mut p2 = Rc::new(Box::new(Part2::new()));

    p1.set_left(p2);
    //          ~~
    // rustc: mismatched types
    // expected struct `Rc<Box<(dyn Linkage + 'static)>>`
    // found struct `Rc<Box<Part2>>`
    // `Part2` implements `Linkage` so you could box the found value and coerce it to
    // the trait object `Box<dyn Linkage>`, you will have to change the expected type as well
    p2.set_right(p1);
    p1.call_left();
}

At the end, all my hopes break apart with the message "rustc: mismatched types". Starting with "Part2 implements ..." I only hear gibberisch and can't come up with a fix. Part2 does implement trait linkage. So please, my sweet rustc, use it!
I can't come up with a different solution to avoid linked lists that do communicate with each other. Some kind of proxy that knows who follows whom only ends with other back-and-forth-references.

Yes, I have asked a similar question already and implemented the answer. But alas, didn't try it out completely. Trying to set the link back ended in a double borrow. I do understand the reasons why Rust is so picky about that. But reference counting and boxes should fix that. Right?

TIA for any help.

PS: I tried it with brute force and unsafe { std::ptr::copy(...) }, but Rust was stronger.

You can't unsize coerce nested objects, so perform the coercion earlier.

-    let mut p1 = Rc::new(Box::new(Part1::new()));
-    let mut p2 = Rc::new(Box::new(Part2::new()));
+    let mut p1 = Rc::new(Box::new(Part1::new()) as Box<dyn Linkage>);
+    let mut p2 = Rc::new(Box::new(Part2::new()) as Box<dyn Linkage>);

That gets further, but opens up a new can of errors which I didn't look into.

2 Likes

Alternatively, use Rc<dyn Linkage> insteadβ€” The Box isn’t really gaining you anything.

The most critical of these is probably:

error[E0596]: cannot borrow data in an `Rc` as mutable
 --> src/main.rs:97:5
  |
97 |     p1.set_left(p2);
  |     ^^ cannot borrow as mutable
  |
  = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Part1>`

Rc is incompatible with taking an exclusive &mut reference because it’s designed to keep multiple references alive. You’ll need to add some internal mutability, like RefCell, somewhere and make all of your trait methods accept &self instead of &mut self.

3 Likes

Honestly, if I were to create a heap-allocated doubly-linked list structure, I would likely just go straight to using unsafe and raw pointers.

Have you considered using the LinkedList in the standard library though?

1 Like

Why do you need to, when the std library has a LinkedList?

To implement a doubly linked list you need to learn how to write unsafe code and use raw pointers, which is a very advanced topic and should be the end of your Rust learning journey, not the beginning. I don't see how it's possible to be successful that way, but you are welcome to try of course!

2 Likes

Have you read Learn Rust With Entirely Too Many Linked Lists? It discusses different approaches and pitfalls at length.

Imho the easiest way to write a linked list is to have all nodes allocated in a single array, with array indices instead of raw pointers to linked nodes.

Also, are you really sure that you need a doubly linked list? You really need O(1) node insertion and deletion based on an existing cursor? Somehow I doubt it. Most likely your use case can be served perfectly fine with a simple Vec.

7 Likes

Have you considered to have HashMap<Id, T> instead of linked lists?

SlotMap (crates.io: Rust Package Registry) is an even better choice for that route. I'm using it for a graph structure. (I forget why, but there was some reason I didn't use PetGraph(crates.io: Rust Package Registry) )

But really check out that too many linked lists article.

2 Likes

I tried, but the structs that are linked are not the same. They just share a common trait.
So it would have to look like this:

    let list: LinkedList<dyn Linkage> = LinkedList::new();

The linked list seemed to be the most obvious, as each node needs to communicate with his neighbors. If the communication/computation has reached the end of the list, the last node will send back a result and each node will add his result (that is a quite roughly description).

Well, let's see ...

    let mut list = Vec::new();
    list.push(PartA::new());
    list.push(PartB::new());
rustc: mismatched types
expected `PartA`, found `PartB`

I need a way to link structs that just share a common trait (I try to avoid the word "subclassing" and "virtual methods"). I could solve it with a single struct that can play different roles that uses a match on that role. But that looks uggly like hell. I'd have to implement a match for each of the maybe 10 (time will show) functions that are needed for communication between each other.

If you want, you can call me naive. I do not know Rust, but I do learn it and want to learn it. I do understand the borrowing and like it.
When I thought about the project in the initial planning, I saw no obstacles. I also decided to learn a new language "along the way" and learn it by implementing something real. That's the best way for me to learn something. Have a given problem and solve it. As soon as I learned something new in Rust, I go over the existing code and refactor it and feel happy. I'm bad at learning with abstract theory. I need a pattern to work with.

NB:
I took a few days off and thought about other ways (that failed).
At least some completely and funny positive result:
I got a laptop for free that stopped working after half an hour or one. Then you had to let it rest over night and it worked again. After some tinkering, I found out that it was the Hal-sensor of the lid switch. I just unsoldered it without replacing it (43 cent part). Now the laptop works. And it came with a brand new battery. Just closing the lid isn't detected anymore. But there is a fn-key for the sleep mode.
I installed Linux and Rust on it. :slight_smile:
So, problem solved. On to the next one ...

1 Like

So I had a closer look at std::collections::LinkedList.
I see no possibility to have access to the left or right (prev or next) element from within the list.
Say you have a double linked list like:
PartA <-> PartB <-> PartC
How could have PartA access to the functions (by traits) of PartB? How could PartA know that there is nothing left of him?
LinkedList only has access from the outside. The problem that LinkedList only can hold one type could be solved with a stub that is always the same but holds the specialized PartA/PartB/... in a reference. At least, that was my plan. But due to the lack of a prev/next, I just hit an other roadblock.
I might be able to use the pattern with the stub in a Vect, but then things get more and more obscure and entangled.

I'll have to use brute force I guess ...

There is a nightly only Cursor API with methods like current(), peek_prev(), peek_next(). There's also CursorMut, methods on the linked list give you one or the other.

I don't think there's technically much reason it should remain unstable. Seems to be a lack of usage of LinkedLists that leads to nobody prioritising the stabilisation of the API.

Good luck!

Sure, but you can't get a Cursor from the element pointer without traversing the list to verify that the element actually belongs to this list and not some other list.

Maybe you can use Vec<Box<dyn Trait>>.

1 Like