How to use two traits with enum_dispatch? - rust

I've tried:
#[enum_dispatch(BarTrait, BazTrait)]
pub enum Foo {
VariantZero,
...
}
It seems to ignore any traits after the first, silently.
This causes errors, as in this case the compiler doesn't seem to believe that Foo implements BazTrait.
Update: #kmdreko's code works properly so long as BazTrait is in the same crate as Foo.
When BazTrait is in a different crate, which also uses enum_dispatch, BazTrait is ignored and causes two errors of the form:
error[E0599]: no method named `baz` found for enum `Foo` in the current scope
--> src/main.rs:53:9
|
45 | enum Foo {
| -------- method `baz` not found for this
...
53 | foo.baz();
| ^^^ method not found in `Foo`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `baz`, perhaps you need to implement it:
candidate #1: `mylib::BazTrait`
It is important to note that there is no error at either #[enum_dispatch(BarTrait, BazTrait)] or pub enum Foo {.

The #[enum_dispatch] attribute does not work across crates:
Unfortunately, procedural macros in Rust are limited in a few ways that will make what you're trying to achieve impossible - notably, they are run independently per-crate, so there's no way for information from one crate to affect an implementation in another.
From the author of enum_dispatch in an unrelated issue.

Related

Missing Copy/Debug traits warnings when defining enum with `strum` traits in macro

I want to define an enum using a macro. The enum needs to implement the strum traits {Display, EnumIter, EnumString}. I also want to keep the warnings missing_copy_implementations, missing_debug_implementations on. I came up with the following snippet:
#![warn(
missing_copy_implementations,
missing_debug_implementations,
)]
macro_rules! define_fruits {
{$($fruit:ident -> $name:literal),* $(,)?} => {
#[derive(Display, EnumIter, EnumString, Clone, Copy, Debug)]
pub enum Fruits {
$(
#[strum(to_string = $name)]
$fruit,
)*
}
};
}
define_fruits! {
Apple -> "green",
Orange -> "orange",
}
The above works fine except I get the warnings:
|
4 | missing_copy_implementations,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this warning originates in the macro `define_fruits` (in Nightly builds, run with -Z macro-backtrace for more info)
and similar for missing_debug_implementations.
These warnings go away when I remove Display, EnumIter, EnumString from my enum. They also go away if I define the same enum outside of a macro.
I cannot seem to find a way to get rid of the warnings in the above scenario, can someone help?
TL/DR: the problem is with derive(EnumIter), which isn't compatible with these lints. I've opened the issue on strum repository asking for possible changes.
To see what really happens, let's try to cargo expand the current code. After some simplification and stubbing out the unnecessary parts, we can see the following structure:
///An iterator over the variants of [Self]
pub struct FruitsIter {
idx: usize,
back_idx: usize,
marker: PhantomData<()>,
}
This is a type you get when you call IntoEnumIterator::iter derived for your enum. There's no Copy or Debug implementation for this struct - they are neither derived nor explicitly added by strum, so the lint sees the violation and fires.
A hint on the problem source can be seen when looking at the exact error, as provided by cargo check (or at the eror highlighting from rust-analyzer):
warning: type could implement `Copy`; consider adding `impl Copy`
--> src/lib.rs:10:27
|
10 | #[derive(Display, EnumIter, EnumString, Clone, Copy, Debug)]
| ___________________________^
11 | | pub enum Fruits {
| |___________^
Note that the span of the "erroneous" code starts from the EnumIter - since that's the token which the FruitsIter's span is tied to, warning on the FruitsIter is shown as a warning on the derive(EnumIter). And indeed, if we drop this derive - warning disappears.
There's nothing you can do with this, I'm afraid, aside from explicitly allowing these lints for the whole module containing your enum. This is something that should probably be fixed by the strum maintainers.

how to make external recursive type insertable to a HashSet

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>).
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).

How to use #[wasm_bindgen] with a type aliases?

What I'd like to do:
#[wasm_bindgen]
pub type ParseError = parserange::Error;
But the Rust compiler complains:
error: #[wasm_bindgen] can only be applied to a function, struct, enum, impl, or extern block
--> src/lib.rs:89:1
|
89 | pub type ParseError = parserange::Error;
I don't want to add the #[wasm-bindgen] attribute in parserange.rs because it's in a separate library that doesn't build with wasm.
Is there a work-around?
The workaround is to wrap the parserange::Error in a tuple struct to create a new type. The downside of this is that all functions defined on parserange::Error that you'd like to use will have to be redefined to call the error sub-object's function. This has all the advantages of creating a new type though, such as exporting in through wasm_bindgen, or implementing traits on a type defined in another crate. More information can be found here

Rust says import is not used and can't find imported statements at the same time

I've got some structure I want to use Serde with. Like this one:
use serde::{Serialize, Deserialize};
#[derive(Serialize)]
struct GetLinkResponse {
error: String,
link: String,
}
But compiler says that:
Serialize import is not used
Serialize can't be found
error: cannot find derive macro `Serialize` in this scope
--> src/structs.rs:3:10
|
3 | #[derive(Serialize)]
| ^^^^^^^^^
warning: unused imports: `Deserialize`, `Serialize`
--> src/structs.rs:1:13
|
1 | use serde::{Serialize, Deserialize};
| ^^^^^^^^^ ^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
I guess I'm not understanding something about how use works. Can somebody explain me what is my mistake here?
You're victim of the "magic" behind automatic trait implementation, i.e. procedural macros. Usually, people give the procedural macro that implements a trait the same name as the trait (because it's convenient). Here, there is the trait serde::Serialize, but there is also the procedural macro that automatically implements that trait serde::Serialize.
They don't conflict because they live in difference namespaces, and the compiler infers from the context if it's a macro or a trait.
In your situation, you forgot to add the derive feature, so there is no proc macro in serde. Thus, you're importing a trait without using it, and you try to use a proc macro that doesn't exist.
The solution is to use the serde's derive feature:
serde = { version = "1.0", features = ["derive"] }

Why doesn't `Box::into_raw` take `self` as parameter?

This simple program:
fn main() {
let b: Box<i32> = Box::new(1);
b.into_raw();
}
Produces this inconvenient error when compiled with Rust 1.12.0:
error: no method named `into_raw` found for type `Box<i32>` in the current scope
--> <anon>:3:7
|
3 | b.into_raw();
| ^^^^^^^^
|
= note: found the following associated functions; to be used as methods, functions must have a `self` parameter
= note: candidate #1 is defined in an impl for the type `Box<_>`
This is because into_raw is not defined to take self as parameter, but instead is defined as:
impl Box<T: ?Sized> {
fn into_raw(b: Box<T>) -> *mut T;
}
This seems inconvenient, and I cannot find a rationale.
So... why?
Because 99.995% of the time (statistic totally made up), you expect method calls to happen to the thing being pointed to, not to the pointer itself. As a result, the "smart pointer" types in Rust generally avoid doing anything to break that expectation. An obvious exception would be something like Rc/Arc implementing Clone directly.
Box implements Deref, which means that all methods that are enclosed by the Box are automatically made available; from the outside, Box<T> and T look and act the same.
If into_raw were a method instead of an associated function, it would shadow any into_raw method on the contained type.
There are other examples of these enhancing associated functions on Rc, such as downgrade or try_unwrap, or on Arc, such as make_mut.

Resources