Rust proc_macro's: adding constants - rust

I am trying to add a procedural macro that adds constant field to a struct.
Example:
#[add_const]
struct MyStruct {
id: u32,
name: String
}
// Expands to:
struct MyStruct {
id: u32,
name: String
}
impl MyStruct {
const FIELDS: [&'static str; 2] = ["id", "name"];
const TYPES: [syn::Type; 2] = [/*Types of the fields*/];
}
My code for the procedural macro is the following:
#[proc_macro_attribute]
pub fn add_const(_attrs: TokenStream, input: TokenStream) -> TokenStream {
let item_struct = parse_macro_input!(input as ItemStruct);
let struct_name = &item_struct.ident;
let fields: Vec<(String, syn::Type)>;
if let syn::Fields::Named(ref _fields) = item_struct.fields {
let _fields = &_fields.named;
fields = _fields.iter().map(|field| {
if let Some(ident) = &field.ident {
let field_name: String = ident.to_string();
let field_type = &field.ty;
let entry = (field_name, field_type.to_owned());
entry
} else {
panic!("Only named fields are supported.")
}
}).collect();
} else {
panic!("The row struct has no fields.");
}
let mut field_names: Vec<String> = Vec::new();
let mut field_types: Vec<syn::Type> = Vec::new();
fields.iter().for_each(|field| {
let (_name, _type) = field;
field_names.push(_name.to_owned());
field_types.push(_type.to_owned());
});
TokenStream::from(
quote!(
#item_struct
impl #struct_name {
const FIELDS: [&'static str; #field_len] = [#(#field_names),*];
const TYPES: [syn::Type; #field_len] = [#(#field_types),*];
}
)
)
}
This throws the following errors:
error[E0423]: expected value, found builtin type `i32`
--> tests/table_row.rs:7:13
|
7 | id: i32,
| ^^^ not a value
error[E0423]: expected value, found struct `String`
--> tests/table_row.rs:8:15
|
8 | name: String,
| ^^^^^^ help: use struct literal syntax instead: `String { vec: val }`
|
::: /Users/jonaseveraert/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/string.rs:292:1
|
292 | pub struct String {
| ----------------- `String` defined here
I have been able to iron down where the problem lies;
/*CODE*/
impl #struct_name {
const FIELDS: [&'static str; #field_len] = [#(#field_names),*];
// const TYPES: [syn::Type; #field_len] = [#(#field_types),*]; // Commenting out this line makes the errors disappear
}
/*MORE CODE*/
How can I fix this? i.e. how can I add the second constant without the errors appearing and why do those errors appear?
Thank you in advance,
Jonas

Related

Why value remains borrowed even after its sope ends?

I am trying to break a text intro substrings.
However the Rust compiler issues E0505.
Here is a cut down version of the code:
struct N<'e> {
name: &'e str,
}
struct BigState<'e> {
text: &'e str,
nm: Option<N<'e>>,
}
fn mod_text<'e>(s: &'e mut &'e str) -> N<'e> {
let h = s.split_at(3).0;
*s = s.split_at(3).1;
return N { name: h };
}
fn print_and_destroy(bs: BigState) {
println!("{:}", bs.text);
println!("{:}", bs.nm.unwrap().name);
}
fn main() {
let text = "12345789";
let mut bs = BigState {
text: text,
nm: None,
};
{
let fresh_nm = mod_text(&mut bs.text);
bs.nm = Some(fresh_nm);
}
print_and_destroy(bs);
}
I don't understand why is bs.text still borrowed after the function mod_text returns.
The expected result whould be:
456789
123
What has to give so the code will compile?

Why expected `isize` in the procedural macro `u32_suffixed` error report?

I wrote such a macro with a test example like this:
#[selector]
pub enum Action {
A = "a()",
B = "b()",
}
The selector implementation:
#[proc_macro_attribute]
pub fn selector(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemEnum);
let ItemEnum { attrs, vis, enum_token, ident, variants, .. } = input;
let mut ident_vec: Vec<Ident> = Vec::new();
let mut hash_vec: Vec<Expr> = Vec::new();
let mut count = 0u32;
for variant in variants {
count = count + 1;
if let Some((_, Expr::Lit(lit))) = variant.discriminant {
let ExprLit { attrs, lit } = lit;
if let Lit::Str(lit_str) = lit {
ident_vec.push(variant.ident);
hash_vec.push(Expr::Lit(ExprLit {
attrs: Default::default(),
// lit: Lit::Verbatim(Literal::u32_unsuffixed(count)), // works
lit: Lit::Verbatim(Literal::u32_suffixed(count)), // compile error
}));
}
}
}
(quote! {
#vis #enum_token #ident {
#(
#ident_vec = #hash_vec
),*
}
})
.into()
}
The compile error report:
error[E0308]: mismatched types
--> macros/generate-actions/tests/tests.rs:6:2
|
6 | #[generate_actions::selector]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| expected `isize`, found `u32`
| in this procedural macro expansion
|
::: /home/bear/working/darwinia/play-rust/macros/generate-actions/src/lib.rs:8:1
|
8 | pub fn selector(attr: TokenStream, item: TokenStream) -> TokenStream {
| -------------------------------------------------------------------- in this expansion of `#[generate_actions::selector]`
|
help: change the type of the numeric literal from `u32` to `isize`
|
6 | #[generate_actions::selector]isize
I want to know why u32_suffixed will trigger such a compile error?
The problem here has nothing to do with the fact that you're using a macro. The issue is that the macro generates code similar to this:
enum Foo {
A = 0u32,
}
Playground
Which triggers the same error. However according to the Rust reference, enum discriminants are supposed to have type isize:
enum Foo {
A = 0, // Automatically inferred as `isize`
B = 1isize, // OK
}
Playground

Create vector of trait objects

I am trying to create a vector of trait objects but I'm getting a type mismatch. I also tried using .map() instead of a for loop but had the same problem. This is a minimal version of my real code, I realise this example doesn't require the use of trait objects but my real code does since there will be types other than Link in the List, which also implement Speak. How can I create a Vec with the correct type so I can pass it to trait_objects?
use std::fs;
fn main() {
// let links = fs::read_dir("my_dir").unwrap();
// let links: Vec<Box<dyn Speak>> = links
// .map(|file| {
// let myfile = file.unwrap();
// let href = myfile.path().to_str().unwrap();
// Box::new(Link { attr: href })
// })
// .collect();
let links = Vec::new();
for entry in fs::read_dir("my_dir").unwrap() {
let myfile = entry.unwrap();
let href = myfile.path().to_str().unwrap();
links.push(Box::new(Link { attr: href }));
}
let link_objects = ListOfTraitObjects {
trait_objects: links,
};
for link in link_objects.trait_objects.iter() {
println!("{}", link.speak());
}
}
trait Speak {
fn speak(&self) -> String;
}
struct ListOfTraitObjects {
trait_objects: Vec<Box<dyn Speak>>,
}
struct Link<'a> {
attr: &'a str,
}
impl Speak for Link<'_> {
fn speak(&self) -> String {
self.attr.to_string()
}
}
There are several issues with your code. The first is that you're creating a Vec<Box<Link>> instead of a Vec<Box<Speak>>. By default Box::new (foo) creates a box of the same type as foo. If you want a box of some trait implemented by foo, you need to be explicit:
links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);
Playground
However this gives two new errors:
error[E0716]: temporary value dropped while borrowed
--> src/lib.rs:7:20
|
7 | let href = myfile.path().to_str().unwrap();
| ^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
8 | links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);
| ----------------------------- cast requires that borrow lasts for `'static`
error[E0596]: cannot borrow `links` as mutable, as it is not declared as mutable
--> src/lib.rs:8:9
|
4 | let links = Vec::new();
| ----- help: consider changing this to be mutable: `mut links`
...
8 | links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
The first error is because your Link struct borrows a string from entry, but entry only lives as long as the current loop iteration. Even though the error states that the cast requires a 'static lifetime, you would get a similar error if you were simply trying to build a Vec<Link> without casting (playground). You need to change the definition of your Link struct to contain an owned string:
struct Link {
attr: String,
}
impl Speak for Link {
fn speak(&self) -> String {
self.attr.clone()
}
}
The second error is easier and can be fixed simply by following the compiler suggestion and replacing let links = … with let mut links = ….
Full working example:
use std::fs;
fn main() {
let mut links = Vec::new();
for entry in fs::read_dir("my_dir").unwrap() {
let myfile = entry.unwrap();
let href = myfile.path().to_str().unwrap().to_string();
links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);
}
let link_objects = ListOfTraitObjects {
trait_objects: links,
};
for link in link_objects.trait_objects.iter() {
println!("{}", link.speak());
}
}
trait Speak {
fn speak(&self) -> String;
}
struct ListOfTraitObjects {
trait_objects: Vec<Box<dyn Speak>>,
}
struct Link {
attr: String,
}
impl Speak for Link {
fn speak(&self) -> String {
self.attr.clone()
}
}
Playground
The same fixes can be applied to your iterator-based solution:
let links = fs::read_dir("my_dir").unwrap();
let links: Vec<_> = links
.map(|file| {
let myfile = file.unwrap();
let href = myfile.path().to_str().unwrap().to_string();
Box::new(Link { attr: href }) as Box<dyn Speak>
})
.collect();
Playground

Is there a way to use internal symbols in a Rust macro?

Suppose my library definespub struct MyStruct { a: i32 }. Then, suppose I define a procedural macro my_macro!. Perhaps I want my_macro!(11) to expand to MyStruct { a: 11 }. This proves that I must have used my_macro! to obtain MyStruct, since the a field is not public.
This is what I want to do:
lib.rs:
struct MyStruct {
a: i32,
}
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let ast: syn::Expr = syn::parse(input).unwrap();
impl_my_macro(ast)
}
fn impl_my_macro(expr: syn::Expr) -> TokenStream {
let gen = match expr {
syn::Expr::Lit(expr_lit) => match expr_lit.lit {
syn::Lit::Int(lit_int) => {
let value = lit_int.base10_parse::<i32>().unwrap();
if value > 100 {
quote::quote! {
compile_error("Integer literal is too big.");
}
} else {
quote::quote! {
MyStruct{a: #lit_int}
}
}
}
_ => quote::quote! {
compile_error!("Expected an integer literal.");
},
},
_ => quote::quote! {
compile_error!("Expected an integer literal.");
},
};
gen.into()
}
And I would use it like this:
fn test_my_macro() {
let my_struct = secret_macro::my_macro!(10);
}
But the compiler gives this warning:
error[E0422]: cannot find struct, variant or union type `MyStruct` in this scope
--> tests/test.rs:14:21
|
14 | secret_macro::my_macro!(10);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
Is it possible to achieve this in Rust?

How to dereference Uuid type?

I'm using the Uuid crate to give unique ids to instantiate each new version of a Node struct with a unique identifier. Sometimes I'd like to filter these structs using .contains() to check if a struct's id is inside some array of Vec<Uuid>.
use uuid::Uuid;
struct Node {
id: Uuid,
}
impl Node {
fn new() -> Self {
let new_obj = Node {
id: Uuid::new_v4()
};
new_obj
}
fn id(&self) -> Uuid {
self.id
}
}
fn main() {
let my_objs = vec![
Node::new(),
Node::new(),
Node::new(),
Node::new(),
];
let some_ids = vec![my_objs[0].id(), my_objs[3].id()];
}
fn filter_objs(all_items: &Vec<Node>, to_get: &Vec<Uuid>){
for z in to_get {
let wanted_objs = &all_items.iter().filter(|s| to_get.contains(*s.id()) == true);
}
}
However this gives the error:
error[E0614]: type `Uuid` cannot be dereferenced
--> src/main.rs:32:72
|
32 | let wanted_objs = &all_items.iter().filter(|s| to_get.contains(*s.id()) == true);
| ^^^^^^^
How can I enable dereferencing for the Uuid type to solve this problem?
Playground
Uuid doesn't implement the Deref trait so it can't be dereferenced, nor does it need to be since you're trying to pass it as an argument to a function with expects a reference. If you change *s.id() to &s.id() the code compiles:
fn filter_objs(all_items: &Vec<Node>, to_get: &Vec<Uuid>) {
for z in to_get {
let wanted_objs = &all_items
.iter()
// changed from `*s.id()` to `&s.id()` here
.filter(|s| to_get.contains(&s.id()) == true);
}
}
playground

Resources