Compilation error using a Rc<RefCell<T>> with html5ever

Hi all. I'm new to rust programming language and start my journey to become a more advanced user. So I'm trying to implement a DOM tree to master smart pointers but i face a compilation error. I try to solve myself this error and also try to use chatgpt but the answer was the same code that's mine.

Here the code:

Html5ever internal struct:

pub struct ExpandedName<'a> {
    pub ns: &'a Atom<NamespaceStaticSet>,
    pub local: &'a Atom<LocalNameStaticSet>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
#[cfg_attr(feature = "heap_size", derive(HeapSizeOf))]
pub struct QualName {
    pub prefix: Option<Prefix>,
    pub ns: Namespace,
    pub local: LocalName,
}

And my code to implement TreeSink, structs:

pub enum NodeData {
    Document,
    Doctype {
        name: StrTendril,
        public_id: StrTendril,
        system_id: StrTendril,
    },
    Text {
        contents: RefCell<StrTendril>,
    },
    Comment {
        contents: StrTendril,
    },
    Element {
        name: QualName,
        attributes: RefCell<Vec<Attribute>>,
        template_contents: Option<Link>,
        mathml_annotation_xml_integration_point: bool,
    },
    ProcessingInstruction {
        target: StrTendril,
        contents: StrTendril,
    },
}
type Link = Rc<RefCell<Node>>;
type WeakLink = Weak<RefCell<Node>>;
pub struct Node {
    pub data: NodeData,
    pub parent: Option<WeakLink>,
    pub children: Rc<RefCell<Vec<Link>>>,
}

The method that cause the error when implementing trait TreeSink for my tree:

...
type Handle = Link;
type Output = Self;

fn elem_name<'a>(&'a self, target: &'a Self::Handle) -> ExpandedName<'a> {
        return match target.borrow().data {
            NodeData::Element { ref name, .. } => name.expanded(),
            _ => panic!("not an element!"),
        };
    }

I've got this error on name.expanded() : "returns a value referencing data owned by the current function". I tried to break down the problem and I manage to write a function returning a QualName but the problem seems to be with the ExpandedName returned by expanded() method with references &'a Atom despite the lifetime 'a. Any idea on how to solve the error ? Thanks.

It's not possible to return a reference to the inner value of a RefCell independently of the borrow guard.

Just think about it – it wouldn't make sense, and it would be unsound. If the RefCell were to hand out independent references to its interior, then it couldn't track whether it's being borrowed mutably or immutably, and it couldn't
perform the dynamic borrow checking that is the very point of its existence.

The only thing you can do is return the Ref<'_> that guards the cell (or a wrapper type that hides this implementation detail).

1 Like

Not a user of html5ever, so I don't know how to implement TreeSink properly. I guess the parser uses TreeSink::create_element to parse the next element and you put it in your tree there, right? Can you change TreeSink::Handle from Link to NodeData, by returning a clone to the NodeData you created in TreeSink::create_element or would that break some other part of your TreeSink implementation?

Either way, your problem is the RefCell in your TreeSink::Handle type and you need to get rid of it somehow. RefCell::borrow creates a Ref that only lives inside your function scope and not for 'a.


Also as a nit, I think you are overusing RefCell a little in your Node and NodeData types. Conceptually I don't see a reason why the vector in Node::children should be wrapped in an Rc<RefCell<...>>. To me, the Node instance looks like the clear owner of the children, or is there some place else in your code where you need mutable access to the vector, without having ownership of the Node? The NodeData::Text::contents and NodeData::Element::attributes RefCells also seem superfluous to me.

Not really because I want a mutable iterator on the tree with also a Weak link to the node for node's parent.

I continued my testing to resolve the issue between using refcell and using html5ever and this build:

fn test_expanded_name(qual_name: &QualName) -> QualName {
    QualName::new(
        None,
        Namespace::from("https://test.rs"),
        LocalName::from("table"),
    )
}

But not that:

fn test_expanded_name(qual_name: &QualName) -> ExpandedName {
    QualName::new(
        None,
        Namespace::from("https://test.rs"),
        LocalName::from("table"),
    )
    .expanded()
}

=> "cannot return reference to temporary value".

To reparent the childen of a node to another but maybe there is a better way?

I want to master smart pointers, so this is more for practice

I'm not saying change your Node structure. I'm saying change the TreeSink::Handle type to something other than Rc<RefCell<Node>> and return that type from TreeSink::create_element. A sketch:


impl TreeSink for Node {
    type Output = Self;
    type Handle = NodeData; // or some other type without `RefCell`

    fn create_element(
        &mut self,
        name: QualName,
        attrs: Vec<Attribute, Global>,
        flags: ElementFlags,
    ) -> Self::Handle {
        // here create your node somehow

        let res = node.data.clone();

        // add your node as a child to the tree node
        self.children.push(Rc::new(RefCell::new(node));

        res // return clone of `node.data`
    }

    fn elem_name<'a>(&'a self, target: &'a Self::Handle) -> ExpandedName<'a> {
        match target {
            NodeData::Element { ref name, .. } => name.expanded(),
            _ => panic!("not an element!"),
        }
    }
}

The Handle type can be anything you want that has enough information to satisfy the API requirements of the TreeSink trait. It doesn't have to be a reference to the Node in your tree, which doesn't work because of lifetime issues.

2 Likes

I changed the Link and WeakLink type to this:

type Link = Rc<Node>;
type WeakLink = Weak<Node>;

And I kept RefCell and Cell only for the data I want to update in Node structure and NodeData structure. And now it works :slight_smile: You were right, too much RefCell.
Thanks for the help.

2 Likes