I am trying to read from a yaml file with serde, however it keeps giving me the error Error("invalid type: map, expected a YAML tag starting with '!'", line: 2, column: 1)
even though I definitely am using a !. I am using a similar structure to a previous program I made, and in the previous version it works fine. Here I am using a mix of Enum and Struct and in the previous one just Enum so maybe that's the problem.
The code I have so far is as followed:
#[derive(Debug,Deserialize)]
enum Blocdef {
ConfigLink(String,Inputdeck),
}
#[derive(Debug,Deserialize)]
struct Inputdeck {
config: HashMap<String,u64>,
}
fn main() -> Result<(), serde_yaml::Error> {
let s = "
- !ConfigLink['NameTest', Inputdeck:{config: {'00': 63,'01': 56}}]
";
let deck: Result<Blocdef, serde_yaml::Error> = serde_yaml::from_str(&s);
println!("{:?}",deck);
Ok(())
}
Related
I've started to learn Rust last week, by reading books and articles, and trying to convert some code from other languages at the same time.
I came across a situation which I'm trying to exemplify through the code below (which is a simplified version of what I was trying to convert from another language):
#[derive(Debug)]
struct InvalidStringSize;
impl std::fmt::Display for InvalidStringSize {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "string is too short")
}
}
impl std::error::Error for InvalidStringSize {}
pub fn extract_codes_as_ints(
message: String,
) -> Result<(i32, i32, i32), Box<dyn std::error::Error>> {
if message.len() < 20 {
return Err(Box::new(InvalidStringSize {}));
}
let code1: i32 = message[0..3].trim().parse()?;
let code2: i32 = message[9..14].trim().parse()?;
let code3: i32 = message[17..20].trim().parse()?;
Ok((code1, code2, code3))
}
So basically I want to extract 3 integers from specific positions of the given string (I could also try to check the other characters for some patterns, but I've left that part out).
I was wondering, is there a way to "catch" or verify all three results of the parse calls at the same time? I don't want to add a match block for each, I'd just like to check if anyone resulted in an error, and return another error in that case. Makes sense?
The only solution I could think of so far would be to create another function with all parses, and match its result. Is there any other way to do this?
Also, any feedback/suggestions on other parts of the code is very welcome, I'm struggling to find out the "right way" to do things in Rust.
The idiomatic way to accomplish this is to define your own error type and return it, with a From<T> implementation for each error type T that can occur in your function. The ? operator will do .into() conversions to match the error type your function is declared to return.
A boxed error is overkill here; just declare an enum listing all of the ways the function can fail. The variant for an integer parse error can even capture the caught error.
use std::fmt::{Display, Formatter, Error as FmtError};
use std::error::Error;
use std::num::ParseIntError;
#[derive(Debug, Clone)]
pub enum ExtractCodeError {
InvalidStringSize,
InvalidInteger(ParseIntError),
}
impl From<ParseIntError> for ExtractCodeError {
fn from(e: ParseIntError) -> Self {
Self::InvalidInteger(e)
}
}
impl Error for ExtractCodeError {}
impl Display for ExtractCodeError {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
Self::InvalidStringSize => write!(f, "string is too short"),
Self::InvalidInteger(e) => write!(f, "invalid integer: {}", e)
}
}
}
Now we just need to change your function's return type and have it return ExtractCodeError::InvalidStringSize when the length is too short. Nothing else needs to change as a ParseIntError is automatically converted into an ExtractCodeError:
pub fn extract_codes_as_ints(
message: String,
) -> Result<(i32, i32, i32), ExtractCodeError> {
if message.len() < 20 {
return Err(ExtractCodeError::InvalidStringSize);
}
let code1: i32 = message[0..3].trim().parse()?;
let code2: i32 = message[9..14].trim().parse()?;
let code3: i32 = message[17..20].trim().parse()?;
Ok((code1, code2, code3))
}
As an added bonus, callers of this function will be able to inspect errors more easily than with a boxed dyn Error.
In more complex cases, such as where you'd want to tweak the error slightly for each possible occurrence of a ParseIntError, you can use .map_err() on results to transform the error. For example:
something_that_can_fail.map_err(|e| SomeOtherError::Foo(e))?;
I'm trying to use syn to create an AST from a Rust file and then using quote to write it to another. However, when I write it, it puts extra spaces between everything.
Note that the example below is just to demonstrate the minimum reproducible problem I'm having. I realize that if I just wanted to copy the code over I could copy the file but it doesn't fit my case and I need to use an AST.
pub fn build_file() {
let current_dir = std::env::current_dir().expect("Unable to get current directory");
let rust_file = std::fs::read_to_string(current_dir.join("src").join("lib.rs")).expect("Unable to read rust file");
let ast = syn::parse_file(&rust_file).expect("Unable to create AST from rust file");
match std::fs::write("src/utils.rs", quote::quote!(#ast).to_string());
}
The file that it creates an AST of is this:
#[macro_use]
extern crate foo;
mod test;
fn init(handle: foo::InitHandle) {
handle.add_class::<Test::test>();
}
What it outputs is this:
# [macro_use] extern crate foo ; mod test ; fn init (handle : foo :: InitHandle) { handle . add_class :: < Test :: test > () ; }
I've even tried running it through rustfmt after writing it to the file like so:
utils::write_file("src/utils.rs", quote::quote!(#ast).to_string());
match std::process::Command::new("cargo").arg("fmt").output() {
Ok(_v) => (),
Err(e) => std::process::exit(1),
}
But it doesn't seem to make any difference.
The quote crate is not really concerned with pretty printing the generated code. You can run it through rustfmt, you just have to execute rustfmt src/utils.rs or cargo fmt -- src/utils.rs.
use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;
fn write_and_fmt<P: AsRef<Path>, S: ToString>(path: P, code: S) -> io::Result<()> {
fs::write(&path, code.to_string())?;
Command::new("rustfmt")
.arg(path.as_ref())
.spawn()?
.wait()?;
Ok(())
}
Now you can just execute:
write_and_fmt("src/utils.rs", quote::quote!(#ast)).expect("unable to save or format");
See also "Any interest in a pretty-printing crate for Syn?" on the Rust forum.
As Martin mentioned in his answer, prettyplease can be used to format code fragments, which can be quite useful when testing proc macro where the standard to_string() on proc_macro2::TokenStream is rather hard to read.
Here a code sample to pretty print a proc_macro2::TokenStream parsable as a syn::Item:
fn pretty_print_item(item: proc_macro2::TokenStream) -> String {
let item = syn::parse2(item).unwrap();
let file = syn::File {
attrs: vec![],
items: vec![item],
shebang: None,
};
prettyplease::unparse(&file)
}
I used this in my tests to help me understand where is the wrong generated code:
assert_eq!(
expected.to_string(),
generate_event().to_string(),
"\n\nActual:\n {}",
pretty_print_item(generate_event())
);
Please see the new prettyplease crate. Advantages:
It can be used directly as a library.
It can handle code fragments while rustfmt only handles full files.
It is fast because it uses a simpler algorithm.
Similar to other answers, I also use prettyplease.
I use this little trick to pretty-print a proc_macro2::TokenStream (e.g. what you get from calling quote::quote!):
fn pretty_print(ts: &proc_macro2::TokenStream) -> String {
let file = syn::parse_file(&ts.to_string()).unwrap();
prettyplease::unparse(&file)
}
Basically, I convert the token stream to an unformatted String, then parse that String into a syn::File, and then pass that to prettyplease package.
Usage:
#[test]
fn it_works() {
let tokens = quote::quote! {
struct Foo {
bar: String,
baz: u64,
}
};
let formatted = pretty_print(&tokens);
let expected = "struct Foo {\n bar: String,\n baz: u64,\n}\n";
assert_eq!(formatted, expected);
}
I have a custom struct like the following:
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
Is it possible to get the number of struct fields programmatically (like, for example, via a method call field_count()):
let my_struct = MyStruct::new(10, "second_field", 4);
let field_count = my_struct.field_count(); // Expecting to get 3
For this struct:
struct MyStruct2 {
first_field: i32,
}
... the following call should return 1:
let my_struct_2 = MyStruct2::new(7);
let field_count = my_struct2.field_count(); // Expecting to get count 1
Is there any API like field_count() or is it only possible to get that via macros?
If this is achievable with macros, how should it be implemented?
Are there any possible API like field_count() or is it only possible to get that via macros?
There is no such built-in API that would allow you to get this information at runtime. Rust does not have runtime reflection (see this question for more information). But it is indeed possible via proc-macros!
Note: proc-macros are different from "macro by example" (which is declared via macro_rules!). The latter is not as powerful as proc-macros.
If this is achievable with macros, how should it be implemented?
(This is not an introduction into proc-macros; if the topic is completely new to you, first read an introduction elsewhere.)
In the proc-macro (for example a custom derive), you would somehow need to get the struct definition as TokenStream. The de-facto solution to use a TokenStream with Rust syntax is to parse it via syn:
#[proc_macro_derive(FieldCount)]
pub fn derive_field_count(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
// ...
}
The type of input is ItemStruct. As you can see, it has the field fields of the type Fields. On that field you can call iter() to get an iterator over all fields of the struct, on which in turn you could call count():
let field_count = input.fields.iter().count();
Now you have what you want.
Maybe you want to add this field_count() method to your type. You can do that via the custom derive (by using the quote crate here):
let name = &input.ident;
let output = quote! {
impl #name {
pub fn field_count() -> usize {
#field_count
}
}
};
// Return output tokenstream
TokenStream::from(output)
Then, in your application, you can write:
#[derive(FieldCount)]
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
MyStruct::field_count(); // returns 3
It's possible when the struct itself is generated by the macros - in this case you can just count tokens passed into macros, as shown here. That's what I've come up with:
macro_rules! gen {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
gen!(#count $($field),+)
}
}
};
(#count $t1:tt, $($t:tt),+) => { 1 + gen!(#count $($t),+) };
(#count $t:tt) => { 1 };
}
Playground (with some test cases)
The downside for this approach (one - there could be more) is that it's not trivial to add an attribute to this function - for example, to #[derive(...)] something on it. Another approach would be to write the custom derive macros, but this is something that I can't speak about for now.
I've read this documentation page but I'm still unable to figure out how to do this.
My files are:
|- pancakes.rs
|- main.rs
I'm deriving on the struct Pancakes in "pancakes.rs":
#[derive(HelloWorld)]
struct Pancakes;
I have the following implementation copied from the documentation, but the ident does not contain the full "namespace":
#[proc_macro_derive(HelloWorld)]
pub fn hello_world(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_derive_input(&s).unwrap();
let gen = impl_hello_world(&ast);
gen.parse().unwrap()
}
fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
let name = &ast.ident; // <---- HERE name = Pancakes, not pancakes::Pancakes
quote! {
impl HelloWorld for #name {
fn hello_world() {
println!("Hello, World! My name is {}", stringify!(#name));
}
}
}
}
Is it possible to get all the information about the struct? I'd also like to get the cargo's lib name from where the derive is used.
This is not possible. Custom derive work on token streams, from which you can easily build an AST. But at this level, names have not been resolved yet (which makes sense, macros and custom derives can affect how the names are resolved, so they need to be fully expended first).
Serde supports applying custom attributes that are used with #[derive(Serialize)]:
#[derive(Serialize)]
struct Resource {
// Always serialized.
name: String,
// Never serialized.
#[serde(skip_serializing)]
hash: String,
// Use a method to decide whether the field should be skipped.
#[serde(skip_serializing_if = "Map::is_empty")]
metadata: Map<String, String>,
}
I understand how to implement a procedural macro (Serialize in this example) but what should I do to implement #[serde(skip_serializing)]? I was unable to find this information anywhere. The docs don't even mention this. I have tried to look at the serde-derive source code but it is very complicated for me.
First you must register all of your attributes in the same place you register your procedural macro. Let's say we want to add two attributes (we still don't talk what will they belong to: structs or fields or both of them):
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))]
pub fn fxsm(input: TokenStream) -> TokenStream {
// ...
}
After that you may already compile your user code with the following:
#[derive(Copy, Clone, Debug, FiniteStateMachine)]
#[state_change(GameEvent, change_condition)] // optional
enum GameState {
#[state_transitions(NeedServer, Ready)]
Prepare { players: u8 },
#[state_transitions(Prepare, Ready)]
NeedServer,
#[state_transitions(Prepare)]
Ready,
}
Without that compiler will give a error with message like:
state_change does not belong to any known attribute.
These attributes are optional and all we have done is allow them to be to specified. When you derive your procedural macro you may check for everything you want (including attributes existence) and panic! on some condition with meaningful message which will be told by the compiler.
Now we will talk about handling the attribute! Let's forget about state_transitions attribute because it's handling will not vary too much from handling struct/enum attributes (actually it is only a little bit more code) and talk about state_change. The syn crate gives you all the needed information about definitions (but not implementations unfortunately (I am talking about impl here) but this is enough for handling attributes of course). To be more detailed, we need syn::DeriveInput, syn::Body, syn::Variant, syn::Attribute and finally syn::MetaItem.
To handle the attribute of a field you need to go through all these structures from one to another. When you reach Vec<syn:: Attribute> - this is what you want, a list of all attributes of a field. Here our state_transitions can be found. When you find it, you may want to get its content and this can be done by using matching syn::MetaItem enum. Just read the docs :) Here is a simple example code which panics when we find state_change attribute on some field plus it checks does our target entity derive Copy or Clone or neither of them:
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))]
pub fn fxsm(input: TokenStream) -> TokenStream {
// Construct a string representation of the type definition
let s = input.to_string();
// Parse the string representation
let ast = syn::parse_derive_input(&s).unwrap();
// Build the impl
let gen = impl_fsm(&ast);
// Return the generated impl
gen.parse().unwrap()
}
fn impl_fsm(ast: &syn::DeriveInput) -> Tokens {
const STATE_CHANGE_ATTR_NAME: &'static str = "state_change";
if let syn::Body::Enum(ref variants) = ast.body {
// Looks for state_change attriute (our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == STATE_CHANGE_ATTR_NAME) {
if let syn::MetaItem::List(_, ref nested) = a.value {
panic!("Found our attribute with contents: {:?}", nested);
}
}
// Looks for derive impls (not our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == "derive") {
if let syn::MetaItem::List(_, ref nested) = a.value {
if derives(nested, "Copy") {
return gen_for_copyable(&ast.ident, &variants, &ast.generics);
} else if derives(nested, "Clone") {
return gen_for_clonable(&ast.ident, &variants, &ast.generics);
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("How have you been able to call me without derive!?!?");
}
} else {
panic!("Finite State Machine must be derived on a enum.");
}
}
fn derives(nested: &[syn::NestedMetaItem], trait_name: &str) -> bool {
nested.iter().find(|n| {
if let syn::NestedMetaItem::MetaItem(ref mt) = **n {
if let syn::MetaItem::Word(ref id) = *mt {
return id == trait_name;
}
return false
}
false
}).is_some()
}
You may be interested in reading serde_codegen_internals, serde_derive, serenity's #[command] attr, another small project of mine - unique-type-id, fxsm-derive. The last link is actually my own project to explain to myself how to use procedural macros in Rust.
After some Rust 1.15 and updating the syn crate, it is no longer possible to check derives of a enums/structs, however, everything else works okay.
You implement attributes on fields as part of the derive macro for the struct (you can only implement derive macros for structs and enums).
Serde does this by checking every field for an attribute within the structures provided by syn and changing the code generation accordingly.
You can find the relevant code here: https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/attr.rs
To expand Victor Polevoy's answer when it comes to the state_transitions attribute. I'm providing an example of how to extract the field attribute #[state_transitions(NeedServer, Ready)] on a enum that derives #[derive(FiniteStateMachine)]:
#[derive(FiniteStateMachine)]
enum GameState {
#[state_transitions(NeedServer, Ready)] // <-- extract this
Prepare { players: u8 },
#[state_transitions(Prepare, Ready)]
NeedServer,
#[state_transitions(Prepare)]
Ready,
}
use proc_macro::TokenStream;
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions))]
pub fn finite_state_machine(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
// Extract the enum variants
let variants: Vec<&syn::Variant> = match &ast.data {
syn::Data::Enum(ref data_enum) => data_enum.variants.iter().collect(),
other => panic!("#[derive(FiniteStateMachine)] expects enum, got {:#?}", other)
};
// For each variant, extract the attributes
let _ = variants.iter().map(|variant| {
let attrs = variant.attrs.iter()
// checks attribute named "state_transitions(...)"
.find_map(|attr| match attr.path.is_ident("state_transitions") {
true => Some(&attr.tokens),
false => None,
})
.expect("#[derive(FiniteStateMachine)] expects attribute macros #[state_transitions(...)] on each variant, found none");
// outputs: attr: "(NeedServer, Ready)"
eprintln!("attr: {:#?}", attrs.to_string());
// do something with the extracted attributes
...
})
.collect();
...
}
The content of the extracted attrs (typed TokenStream) looks like this:
TokenStream [
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
ident: "NeedServer",
span: #0 bytes(5511..5521),
},
Punct {
ch: ',',
spacing: Alone,
span: #0 bytes(5521..5522),
},
Ident {
ident: "Ready",
span: #0 bytes(5523..5528),
},
],
span: #0 bytes(5510..5529),
},
]