2

I use https://github.com/dtolnay/clang-ast to parse the JSON produced by Clang representing an AST to be available as a Rust data type, specifically the Node. I'd like to insert the nodes from the tree (Node<T> is recursive structure) into a HashSet. I could not even insert the root note:

use std::collections::HashSet;
use log::debug;
use std::env;
use serde::Deserialize;

pub type Node = clang_ast::Node<Clang>;

#[derive(Deserialize)]
#[derive(Debug)]
pub enum Clang {
    BinaryOperator(BinaryOperator),
    Other,
}

#[derive(Deserialize, Debug)]
pub struct BinaryOperator {
    pub opcode: String,
    pub range: clang_ast::SourceRange,
}

fn main() {
    env_logger::init();

    let json = std::fs::read_to_string("ast.json").unwrap();
    let node :Node = serde_json::from_str(&json).unwrap();
    let mut node_set = HashSet::new();
    node_set.insert(node);
}

this fails the compilation with:

error[E0277]: the trait bound `clang_ast::Node<Clang>: Eq` is not satisfied
   --> src/main.rs:28:21
    |
28  |     node_set.insert(node);
    |              ------ ^^^^ the trait `Eq` is not implemented for `clang_ast::Node<Clang>`
    |              |
    |              required by a bound introduced by this call
    |
note: required by a bound in `HashSet::<T, S>::insert`
...

So, I tried to add the Eq and PartialEq implementations using (following some advice from How to implement Eq and Hash for my own structs to use them as a HashMap key?):

impl PartialEq for Node {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for Node {}

however this fails with:

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
 --> src/main.rs:8:1
  |
8 | impl PartialEq for Node {
  | ^^^^^---------^^^^^----
  | |    |             |
  | |    |             `clang_ast::Node` is not defined in the current crate
  | |    `clang_ast::Node` is not defined in the current crate
  | impl doesn't use only types from inside the current crate
  |
  = note: define and implement a trait or new type instead

How do I make this work ? Also, why does the language/compiler imposes such limit ?

Following the answers on Implement foreign trait for foreign type does not really help because there are more pieces to the puzzle (implementing Eq, PartialEq, the Hash trait, dealing with the recursive nature of clang_ast::Note<T>).

Vlad
  • 156
  • 12
  • Does this answer your question? [Implement foreign trait for foreign type](https://stackoverflow.com/questions/35160995/implement-foreign-trait-for-foreign-type) – Chayim Friedman Mar 16 '22 at 11:41
  • Not really. Firstly because I'd like to get as concrete answer as possible. Secondly because `clang_ast::Node` is a recursive structure which makes this a bit more complicated, I believe. – Vlad Mar 16 '22 at 19:31
  • Recursive structs indeed complicates this, but there is not much to do with that. The linked answer is still the solution. – Chayim Friedman Mar 17 '22 at 08:42
  • It might be on generic level, however I am at loss w.r.t. how to apply the wrapped type for the inner field of the wrapped type. – Vlad Mar 17 '22 at 10:21
  • You can't. That's simple. You have to implement `PartialEq` manually. – Chayim Friedman Mar 17 '22 at 10:36
  • Um, how do I do that exactly ? – Vlad Mar 17 '22 at 12:19
  • I already implemented `PartialEq` for the `Node` (the wrapped type - as suggested below) however that does not solve the problem for the inner field of `Node`. – Vlad Mar 17 '22 at 12:25
  • Wrap them too (you can provide getters to do that easily). – Chayim Friedman Mar 17 '22 at 12:42
  • How do I do that exactly ? It all sounds but easy to me. – Vlad Mar 17 '22 at 13:16

1 Answers1

-1

How do I make this work ?

Define a newtype wrapper e.g. struct Node(clang:ast::Node<Clang>).

, or implement the trait on clang_ast::Node<Clang> rather than just clang_ast::Node1.

Also, why does the language/compiler imposes such limit ?

Coherence. There should be only one implementation of a trait for a struct (ignoring specialisation). Allowing crates to implement traits they didn't define on types they didn't define is an opportunity for conflicts and incoherences (where one dependency uses one implementation and an other library uses an other incompatible implementation) which could lead to unsafety if they interact with the same objects in different ways (e.g. a generic collection of some sort, like a hashset).

Here though per the implementation coherence rules since Clang is a local type as long as there are no uncovered types appearing as one of its parameters it should be fine (I could not test this on the playground as clang_ast is to minor to figure there, but it works fine with Box).

In other contexts (like Box) overlapping can also be an issue, but here clang_ast::Node does not implement PartialEq at all, not even conditionally. Until that happens there's no issue.


[1]: incidentally you may want to report that the error message is misleading, since Node is a generic type I don't think it makes sense that Rust accepts this attempt. In fact I can not reproduce it, on the playground trying to impl PartialEq for Box fails with "error[E0107]: missing generics for struct Box".

edit: struct out the completely incorrect bits where I thought the coherence rules were less strict than they actually are (which is only the case for Box, Pin, and reference types).

Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • 2
    Note that in the OP code, `Node` is a local alias for `clang_ast::Node` – Jmb Mar 16 '22 at 10:21
  • @Jmb uh you're right and apparently I got confused by the parameters in the implementation coherence rules (aka what the uncovered parameters are, covered parameters refer to the *trait*'s generic parameters not the *type*'s), and the fact that `Box` is a fundamental type and thus has relaxed coherence rules, which the documentation does state and I missed. Thus testing on the playground using `Box` failed me. One day I will learn not to test things with `Box`. – Masklinn Mar 16 '22 at 11:36
  • using `pub struct Node(clang_ast::Node)` , the `PartialEq` trait can be implemented using `self.0.id == other.0.id` so that seems to do the trick. The wrapping however leads to compilation error "the trait bound `Node: Deserialize<'_>` is not satisfied" and as a fresh comer to Rust this is where I hit the wall. It seems I will need to provide implementation of the `Deserialize` trait for the wrapped struct. – Vlad Mar 16 '22 at 12:06
  • @Vlad maybe you could deserialise to a `clang_ast::Node` then wrap in a `Node` before inserting into the hashset, rather than immediately deserialize to a `Node`? (otherwise yes you'll have to implement `Deserialize` to delegate-and-wrap) – Masklinn Mar 16 '22 at 12:27
  • Note that you can probably `#[derive (PartialEq, Deserialize)]` and most other common traits you need on your wrapper struct. – Jmb Mar 16 '22 at 12:53
  • `PartialEq` would require the wrapped type to implement it (which is the entire issue here) no? And `Deserialize` would also require `serde(transparent)` but you're right that it'd work without needing to implement it by hand. – Masklinn Mar 16 '22 at 13:03
  • Thanks for the hints. Using `#[derive (Deserialize)]` and providing implementations of `PartialEq` and `Eq` (empty) allowed me to progress one step further. Now I just need to provide implementation of `Hash` on the wrapped type it seems. – Vlad Mar 16 '22 at 14:06
  • The `impl Hash for Node` works (not sure if `fn hash(&self, state: &mut H) { self.0.id.hash(state)}` is the right thing to do here, though) for this simple case of inserting the root node into HashSet. – Vlad Mar 16 '22 at 19:22
  • For a bit more complex task, though, like collecting a set of nodes from the tree using recursive function (`fn get_nodes(node: &Node) -> HashSet<&Node>`), I need to access the items in the `clang_ast::Node`, specifically `pub inner: Vec>` (it is a recursive structure) and pass them to the recursive function. Not sure what to do there - implement the `new()` method for the wrapped type that would accept `clang_ast::Node` ? – Vlad Mar 16 '22 at 19:22