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?
Related
I have this next_expected_kind method that return the next item of an Iterable<Kind> if it is the expected type, or an error if not.
It works fine for non parameterized types like Kind1, but I don't know how to use it if the type that needs parameters like Kind2.
Something like:
let _val = match s.next_expected_kind(Kind::Kind2(str)) {
Ok(k) => str,
_ => panic!("error"),
};
Is there any tricky to make it?
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d21d5cff42fcca633e95b4915ce2bf1d
#[derive(PartialEq, Eq)]
enum Kind {
Kind1,
Kind2(String),
}
struct S {
kinds: std::vec::IntoIter<Kind>,
}
impl S {
fn next_expected_kind(&mut self, expected: Kind) -> Result<Kind, &str> {
match self.kinds.next() {
Some(k) if k == expected => Ok(k),
_ => Err("not expected"),
}
}
}
fn main() {
let mut s = S {
kinds: vec![Kind::Kind1, Kind::Kind2(String::from("2"))].into_iter(),
};
_ = s.next_expected_kind(Kind::Kind1);
// let _val = s.next_expected_kind(Kind::Kind2(str));
let _val = match s.kinds.next() {
Some(Kind::Kind2(str)) => str,
_ => panic!("not expected"),
};
}
You could use std::mem::discriminant() like this:
use std::mem::{Discriminant, discriminant};
#[derive(Debug, PartialEq, Eq)]
enum Kind {
Kind1,
Kind2(String),
}
struct S {
kinds: std::vec::IntoIter<Kind>,
}
impl S {
fn next_expected_kind(&mut self, expected: Discriminant<Kind>) -> Result<Kind, &str> {
match self.kinds.next() {
Some(k) if discriminant(&k) == expected => Ok(k),
_ => Err("not expected"),
}
}
}
fn main() {
let mut s = S {
kinds: vec![Kind::Kind1, Kind::Kind2(String::from("2"))].into_iter(),
};
_ = dbg!(s.next_expected_kind(discriminant(&Kind::Kind1)));
let _val = dbg!(s.next_expected_kind(discriminant(&Kind::Kind2(String::new()))));
}
The obvious drawback being that you'll have to create an instance with "empty" or default data wherever you want to call it.
The only other way I can think of would be to write a macro since you can't pass just the "variant" of an enum around.
#[derive(Debug, PartialEq, Eq)]
enum Kind {
Kind1(String),
Kind2(i32, i32),
}
struct S {
kinds: std::vec::IntoIter<Kind>,
}
macro_rules! next_expected_kind {
($self:expr, $expected:path) => {
match $self.kinds.next() {
Some(k) if matches!(k, $expected(..)) => Ok(k),
_ => Err("not expected"),
}
}
}
fn main() {
let mut s = S {
kinds: vec![Kind::Kind1(String::from("1")), Kind::Kind2(2,3)].into_iter(),
};
_ = dbg!(next_expected_kind!(&mut s, Kind::Kind1));
let _val = dbg!(next_expected_kind!(&mut s, Kind::Kind2));
}
Note: this has the limitation that all variants have to be tuple variants or struct variants and it's a bit clunky to use.
I am implementing a derive macro to reduce the amount of boilerplate I have to write for similar types.
I want the macro to operate on structs which have the following format:
#[derive(MyTrait)]
struct SomeStruct {
records: HashMap<Id, Record>
}
Calling the macro should generate an implementation like so:
impl MyTrait for SomeStruct {
fn foo(&self, id: Id) -> Record { ... }
}
So I understand how to generate the code using quote:
#[proc_macro_derive(MyTrait)]
pub fn derive_answer_fn(item: TokenStream) -> TokenStream {
...
let generated = quote!{
impl MyTrait for #struct_name {
fn foo(&self, id: #id_type) -> #record_type { ... }
}
}
...
}
But what is the best way to get #struct_name, #id_type and #record_type from the input token stream?
One way is to use the venial crate to parse the TokenStream.
use proc_macro2;
use quote::quote;
use venial;
#[proc_macro_derive(MyTrait)]
pub fn derive_answer_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Ensure it's deriving for a struct.
let s = match venial::parse_declaration(proc_macro2::TokenStream::from(item)) {
Ok(venial::Declaration::Struct(s)) => s,
Ok(_) => panic!("Can only derive this trait on a struct"),
Err(_) => panic!("Error parsing into valid Rust"),
};
let struct_name = s.name;
// Get the struct's first field.
let fields = s.fields;
let named_fields = match fields {
venial::StructFields::Named(named_fields) => named_fields,
_ => panic!("Expected a named field"),
};
let inners: Vec<(venial::NamedField, proc_macro2::Punct)> = named_fields.fields.inner;
if inners.len() != 1 {
panic!("Expected exactly one named field");
}
// Get the name and type of the first field.
let first_field_name = &inners[0].0.name;
let first_field_type = &inners[0].0.ty;
// Extract Id and Record from the type HashMap<Id, Record>
if first_field_type.tokens.len() != 6 {
panic!("Expected type T<R, S> for first named field");
}
let id = first_field_type.tokens[2].clone();
let record = first_field_type.tokens[4].clone();
// Implement MyTrait.
let generated = quote! {
impl MyTrait for #struct_name {
fn foo(&self, id: #id) -> #record { *self.#first_field_name.get(&id).unwrap() }
}
};
proc_macro::TokenStream::from(generated)
}
I'm writing a compiler in Rust and, given a string, part of the logic is to find out of which "kind" the characters are.
I want to return the "value" of each character. For an input of 1 + 2 each character has a "token" and should return something like:
NumberToken, 1
WhiteSpaceToken, ' '
PlusToken, '+'
WhiteSpaceToken, ' '
NumberToken, 1
My function should return something like
enum SyntaxKind {
NumberToken,
WhiteSpaceToken,
PlusToken
}
struct SyntaxToken {
kind: SyntaxKind,
value: // Some general type
}
fn next_token(line: String) -> SyntaxToken {
// Logic goes here
}
How would I implement such logic?
If you're writing out a tokenizer and you wonder what such logic might look like, you can couple these values in the same enum, e.g:
#[derive(Debug)]
enum Token {
Add,
Sub,
Whitespace,
Number(f64),
}
For more, see The Rust Programming Language, "Defining an Enum" on adding data to variants.
… and then you can use a match inside of an iterator to handle it accordingly:
#[derive(Debug)]
enum Token {
Add,
Sub,
Whitespace,
Number(f64),
}
use std::str::Chars;
use std::iter::Peekable;
struct Tokens<'a> {
source: Peekable<Chars<'a>>,
}
pub type TokenIterator<'a> = Peekable<Tokens<'a>>;
impl<'a> Tokens<'a> {
pub fn new(s: &'a str) -> TokenIterator {
Self {
source: s.chars().peekable(),
}
.peekable()
}
}
impl<'a> Iterator for Tokens<'a> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
match self.source.next() {
Some(' ') => Some(Token::Whitespace),
Some('+') => Some(Token::Add),
Some('-') => Some(Token::Sub),
n # Some('0'..='9') => {
let mut number = String::from(n.unwrap());
while let Some(n) = self.source.next_if(char::is_ascii_digit) {
number.push(n);
}
Some(Token::Number(number.parse::<f64>().unwrap()))
}
Some(_) => unimplemented!(),
None => None,
}
}
}
fn main() {
let tokens = Tokens::new("1 + 2");
for token in tokens {
println!("{:?}", token);
}
}
This should then give you:
Number(1.0)
Whitespace
Add
Whitespace
Number(2.0)
Playground
As per the Serde specification, an Object / Map<String, Value> is a Value:
pub enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
Yet when I compile this code:
extern crate serde;
#[macro_use]
extern crate serde_json;
#[derive(Debug)]
struct Wrapper {
ok: bool,
data: Option<serde_json::Value>,
}
impl Wrapper {
fn ok() -> Wrapper {
Wrapper {
ok: true,
data: None,
}
}
pub fn data(&mut self, data: serde_json::Value) -> &mut Wrapper {
self.data = Some(data);
self
}
pub fn finalize(self) -> Wrapper {
self
}
}
trait IsValidWrapper {
fn is_valid_wrapper(&self) -> bool;
}
impl IsValidWrapper for serde_json::Map<std::string::String, serde_json::Value> {
fn is_valid_wrapper(&self) -> bool {
self["ok"].as_bool().unwrap_or(false)
}
}
fn main() {
let json = json!({
"name": "John Doe",
"age": 43,
"phones": [
"+44 1234567",
"+44 2345678"
]
});
let converted_json: Wrapper = json
.as_object()
.map_or_else(
|| Err(json),
|obj| {
if obj.is_valid_wrapper() {
Ok(Wrapper::ok().data(obj["data"].clone()).finalize())
} else {
Err(*obj as serde_json::Value)
}
},
)
.unwrap_or_else(|data| Wrapper::ok().data(data.clone()).finalize());
println!(
"org json = {:?} => converted json = {:?}",
json, converted_json
);
}
I get this error:
error[E0605]: non-primitive cast: `serde_json::Map<std::string::String, serde_json::Value>` as `serde_json::Value`
--> src/main.rs:60:25
|
60 | Err(*obj as serde_json::Value)
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait
Is there a way to downcast a Map into a Value?
an Object / Map<String, Value> is a Value
No, it is not. Value is a type. Map<String, Value> is a type. Value::Object is an enum variant, which is not a separate type. In this case, Value::Object holds another value of type Map<String, Value>. You have to wrap the value in the variant to convert the type:
Err(serde_json::Value::Object(obj))
This will lead you to the problem:
error[E0308]: mismatched types
--> src/main.rs:57:55
|
57 | Err(serde_json::Value::Object(obj))
| ^^^ expected struct `serde_json::Map`, found reference
|
= note: expected type `serde_json::Map<std::string::String, serde_json::Value>`
found type `&serde_json::Map<std::string::String, serde_json::Value>`
as_object returns a reference to the contained object (if it's present), not the value itself. You will need to match on it for now:
let converted_json = match json {
serde_json::Value::Object(obj) => {}
_ => {}
};
Something like this:
let converted_json = match json {
serde_json::Value::Object(obj) => {
if obj.is_valid_wrapper() {
let mut w = Wrapper::ok();
w.data(obj["data"].clone());
Ok(w.finalize())
} else {
Err(serde_json::Value::Object(obj))
}
}
other => Err(other),
};
Here's where I'm starting from:
#[derive(PartialEq)]
enum ControlItem {
A {
name: &'static str,
},
B {
name: &'static str,
},
}
struct Control {
items: Vec<(ControlItem, bool)>,
}
impl Control {
pub fn set(&mut self, item: ControlItem, is_ok: bool) {
match self.items.iter().position(|ref x| (**x).0 == item) {
Some(idx) => {
self.items[idx].1 = is_ok;
}
None => {
self.items.push((item, is_ok));
}
}
}
pub fn get(&self, item: ControlItem) -> bool {
match self.items.iter().position(|ref x| (**x).0 == item) {
Some(idx) => return self.items[idx].1,
None => return false,
}
}
}
fn main() {
let mut ctrl = Control { items: vec![] };
ctrl.set(ControlItem::A { name: "a" }, true);
assert_eq!(ctrl.get(ControlItem::A { name: "a" }), true);
ctrl.set(ControlItem::B { name: "b" }, false);
assert_eq!(ctrl.get(ControlItem::B { name: "b" }), false);
}
I have a Control type that should save the state of some predefined items and report it back to user.
I have a virtual table in my mind, like this:
|Name in program | Name for user |
|item_1 | Item one bla-bla |
|item_2 | Item two bla-bla |
|item_3 | Item three another-bla-bla|
I want Control to have get / set methods that accept only things with names item_1, item_2, item_3.
I want to hold this virtual table in two crates: "main" and "platform". Most of the implementation of Control should be in the main crate, and definitions of the items (like item_3) should go into the platform crate. I want to register item_3 at compile time.
Any ideas on how achieve this?
It sounds like you should use a trait, not an enum. You could define a trait and implement it like this:
pub trait ControlItem {
fn name(&self) -> &str;
}
struct A(&'static str);
impl ControlItem for A {
fn name(&self) -> &str {
self.0
}
}
// ... similar struct and impl blocks for other items
Then these structs can be moved into separate crates.
You'd need to change Control to store a Vec<(Box<ControlItem>, bool)>, and either change get and set to take a Box<ControlItem>, or to be generic over T: ControlItem.
Read about traits and trait objects for more.