1

This is a follow-up on the question asked here: Possible to implement dynamically-typed linked list in safe Rust?

I successfully implemented a dynamic type LinkedList using the std::any::Any trait.

However, I want to challenge myself by trying to implement it in another way, e.g. using generic type - Node where T can be any type, u32, u64, String, ...

Example

Node<String> -> Node<u32> -> Node<u64> -> Node<String> -> ...

My approach is to use a trait called Next to give Node<T> the ability to "go next".

Node<T> looks like this.

struct Node<T> {
    data: T,
    next: Option<Rc<RefCell<dyn Next>>>,
}

The trait Next looks like this.

pub trait Next {
    fn borrow_next(&self) -> Option<Ref<dyn Next>>;
    fn set_next(&mut self, next: Rc<RefCell<dyn Next>>);
}

These are the implementation of Next for any Node.

impl<T> Next for Node<T> {
    fn set_next(&mut self, next: Rc<RefCell<dyn Next>>) {
        self.next = Some(next);
    }

    fn borrow_next(&self) -> Option<Ref<dyn Next>> {
        match &self.next {
          None => None,
          Some(stmt) => Some(stmt.borrow()),
        }
    }
}

Here are the implementations for the actual struct Node<T>.

impl<T> Node<T> {
    pub fn new<P>(data: P) -> Node<P> {
        Node::<P> { data, next: None }
    }

    pub fn new_wrapped<P>(data: P) -> Rc<RefCell<Node<P>>> {
        Rc::new(RefCell::new(Node::<P>::new(data)))
    }

    pub fn into_wrapped(self) -> Rc<RefCell<Self>> {
        Rc::new(RefCell::new(self))
    }

    pub fn borrow_data(&self) -> &T {
        &self.data
    }

    pub fn set_data(&mut self, data: T) {
        self.data = data;
    }
}

Lastly, the declaration and its implementations of methods of struct DynLinkedList, holding two fields, head and tail, look like this.

struct DynLinkedList {
    head: Option<Rc<RefCell<dyn Next>>>,
    tail: Option<Rc<RefCell<dyn Next>>>,
}


impl DynLinkedList {
    pub fn new_empty() -> Self {
        Self {
            head: None,
            tail: None,
        }
    }

    pub fn new_with_node(node: Rc<RefCell<dyn Next>>) -> Self {
        Self {
            head: Some(node.clone()),
            tail: Some(node),
        }
    }

    pub fn append(&mut self, node: Rc<RefCell<dyn Next>>) {
        self.tail.take().map_or_else(
            || self.head = Some(node.clone()),
            |old_tail| old_tail.borrow_mut().set_next(node.clone()),
        );
        self.tail = Some(node);
    }
}

Here comes the problem:

I am unable to access the data field of Node<T> as it is being treated as a trait object dyn Next by the compiler.

For example, this test would not work:

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_dynll_new_with_node() {
    let node = Node::<u32>::new(77_u32);
    let dynll = DynLinkedList::new_with_node(node.into_wrapped());
    assert_eq!(&dynll.head.unwrap().borrow().borrow_data(), &77);
    assert_eq!(&dynll.tail.unwrap().borrow().borrow_data(), &77)
  }
}

The compiler error is:

error[E0599]: no method named `borrow_data` found for struct `Ref<'_, (dyn Next + 'static)>` in the current scope
   --> src/dyn_ll_idea_five.rs:125:47
    |
125 |     assert_eq!(&*dynll.head.unwrap().borrow().borrow_data(), &77);
    |                                               ^^^^^^^^^^^ method not found in `Ref<'_, (dyn Next + 'static)>`

But, when the .borrow() after .unwrap() returns, it should return an object of type Node which would have the method .borrow_data(), how can I let Rust know that this is the case? Thank you.

I would effectively want to be able to do this:

let mut list = DynLinkedList::new();

list.push_front("hello".to_string());
list.push_back("world".to_string());
list.push_front(123);
list.push_back(456);

assert_eq!(list.pop_front(), Some("hello".to_string()));
assert_eq!(list.pop_back(), Some("world".to_string()));
assert_eq!(list.pop_front(), Some(123));
assert_eq!(list.pop_back(), Some(456));
3
  • You can only use a traits methods on trait objects, you have to move the relevant methods into the Next trait. Commented Jan 6, 2023 at 18:36
  • Okay, but how would I get the data field from the dyn Next trait object? I might try to define a generic method borrow_data<T>(&self) -> Ref<T> but that would mean dyn Next cannot be object-safe anymore. If dyn Next cannot be object-safe, how can I have a "type-less" DynLinkedList struct for my dynamic typed implementation? Commented Jan 6, 2023 at 18:45
  • You can return a trait object describing the type you want or just Ref<dyn Any> for anything. That's a reason why trait objects are somewhat problematic, they lock you into dynamic dispatch and static dispatch is hard/impossible to reach again. Commented Jan 6, 2023 at 19:04

1 Answer 1

3

Well, nowhere in the definition of trait Next does it talk about objects of type Node. Thus, how would the compiler ever know that you can call borrow_data on it? That's where you'd do the downcast via the Any trait.

What's more, the compiler would also want to know which sort of Node we're talking about. Node<i32> or Node<String> or what? And that's downright impossible because your list is dynamic and hence whatever type is contained within a node is also dynamic.

Let's take your example:

Node<String> -> Node<u32> -> Node<u64> -> Node<String> -> ...

So if that's your list, then, using very rough ugly pseudocode, what about this:

let x: String = my_list.head.borrow_data();
let y: u32 = my_list.head.next.borrow_data();
let z: u64 = my_list.head.next.next.borrow_data();

You see the problem here? How is the compiler to know, at compile time, that the third item in the list has type u64? This just isn't a case where generics work in the way you want it.

Sign up to request clarification or add additional context in comments.

3 Comments

Well said. Node<dyn Any> is a much simpler way to achieve the behavior they want.
``` error[E0599]: no method named downcast_ref found for struct Ref<'_, (dyn Next + 'static)> in the current scope --> src/dyn_ll_idea_five.rs:125:47 | 125 | assert_eq!(&*dynll.head.unwrap().borrow().downcast_ref::<Node<u32>>().borrow_data(), &77); | ^^^^^^^^^^^^ method not found in Ref<'_, (dyn Next + 'static)>``` Even if I try downcast_ref::<Node<u32>>(), it still says method is not found even though trait Any` is implemented for Ref.
downcast_ref isn't actually part of the trait interface of Any though; it's part of the implementation, and so I suspect the issue here is that the particular Ref doesn't fit the various type and trait bounds to be considered for that implementation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.