0

I am implementing a linked list in Rust, and so far, the best way I have found to let the nodes point to other nodes or point to nothing is multiple structs that implement the same trait. (Option wouldn't work, because I couldn't figure out how to modify the item inside the Option without consuming the Option.) To make sure all nodes are owned, I have each node own the next node but have a reference to the previous node.

**Yeah, I know my method of accessing the SomeLLElement fields by calling get_LL_element and then unwrapping the Option is quite janky. If you can think of something better, please let me know.

struct EmptyLLElement;
struct SomeLLElement<'a, T> {
    val: T,
    next: Box<dyn LLElement<'a, T>>,
    prev: &'a Box<dyn LLElement<'a, T>>,
}
fn LLEmpty() -> Box<EmptyLLElement> {
    Box::new(EmptyLLElement)
}

trait LLElement<'a, T>{
    fn get_LL_element(self) -> Option<SomeLLElement<'a, T>>;
}

impl <'a, T> LLElement<'a, T> for EmptyLLElement {
    fn get_LL_element(self) -> Option<SomeLLElement<'a, T>> {
        None
    }
}
impl <'a, T> LLElement<'a, T> for SomeLLElement<'a, T> {
    fn get_LL_element(self) -> Option<SomeLLElement<'a, T>> {
        Some(self)
    }
}

fn main(){
    let myvar: Box<dyn LLElement<i32>> = Box::new(SomeLLElement{ val: 1, next: LLEmpty(), prev: &LLEmpty() });
    let myvar2: SomeLLElement<i32> = match myvar.get_LL_element() {
        Some(x) => x,
        None => panic!("Empty!")
    };
    // println!("{}", myvar2.val);
}

The compiler is not allowing me to pass in the empty object (&LLEmpty()) to SomeLLElement.prev.

error[E0308]: mismatched types
  --> src\main.rs:69:97
   |
69 |     let myvar: Box<dyn LLElement<i32>> = Box::new(SomeLLElement{ val: 1, next: LLEmpty(), prev: &LLEmpty() });
   |                                                                                                 ^^^^^^^^^^ expected trait object `dyn LLElement`, found struct `EmptyLLElement`
   |
   = note: expected reference `&Box<(dyn LLElement<'_, {integer}> + 'static)>`
              found reference `&Box<EmptyLLElement>`

If I remove the ampersand from that line

let myvar: Box<dyn LLElement<i32>> = Box::new(SomeLLElement{ val: 1, next: LLEmpty(), prev: LLEmpty() });

and make prev an object instead of a reference,

prev: Box<dyn LLElement<'a, T>>,

--which is not what I want to do, but hey--the error goes away. The compiler should be able to see that EmptyLLElement is an instance of dyn LLElement, right? Shouldn't that also mean that &Box<EmptyLLElement> is compatible for &Box<dyn LLElement>?

  • I don't quite understand what you are trying to do, or what you are trying to ask, but it seems to me that what you need is to implement the trait [Iterator](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html) and use [Peekable](https://doc.rust-lang.org/stable/std/iter/struct.Peekable.html) – al3x Jan 12 '23 at 04:40
  • In some cases you need to explicitly cast into the desired trait object (`&(LLEmpty() as Box>)`). However, the whole trait object approach is definitely not the right tool for implementing a linked list. Also, storing a plain reference to the previous node will not work. The borrow checker will not allow that kind of cyclic reference. You would need to use something like reference counting, a raw pointer, or arena allocation. – Brian Bowman Jan 13 '23 at 04:29

1 Answers1

0

EmptyLLElement is a struct, not a trait, so Box<EmptyLLElement> is just a boxed value, not a trait object. You'd need to change LLEmpty to return a Box<dyn LLElement> or create a trait object from the EmptyLLElement directly in your main function.

You can use options instead of traits for this, however, and it's probably more idiomatic to do so. To mutate the value inside an option without moving it, you can match on it with ref mut:

if let Some(ref mut value) = option {
  // mutate `value` here
}

You might also find Learn Rust With Entirely Too Many Linked Lists a good read.

Jimmy
  • 35,686
  • 13
  • 80
  • 98
  • Just so I understand it better, since `EmptyLLElement` implements the trait `LLElement`, why couldn't it stand in for a trait object like it does for the `next` parameter? I am used to Java, where if a class implements an interface, then an object of that class can be passed in where an object of that interface is required. – Brian Smith Jan 12 '23 at 17:26
  • "Trait object" is different than "a type that implements a trait." A trait object is something you create explicitly by putting the value behind a fat pointer like `Box` and using the `dyn` keyword. A trait object uses dynamic dispatch and includes a vtable to support this. If you instead want `prev` to accept "any type that implements `LLElement`", you'd use generics with trait bounds, e.g. `struct SomeLLElement<'a, L, T> where L: LLElement<'a, T> { prev: &'a L }`. You can then pass in any `L` that implements `LLElement`, and the types will be monomorphized without any dynamic dispatch. – Jimmy Jan 13 '23 at 07:53