How to handle struct with procedural macro? - rust

I need dynamic handling of structure fields and their values. So, I need to dynamically read the name of the structure field and the value it contains for further processing. I understood how to get the name of the field, but I don’t quite understand how I can get the value that is stored in a specific field of the structure.
Something similar does the library serde and validator.
For example i have a struct:
#[derive(StackOverflow)]
struct User {
email: String,
username: String,
password: String,
}
And in another crate I create:
#[proc_macro_derive(StackOverflow, attributes(stackoverflow))]
pub fn derive_stackoverflow(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
...
}

Related

Best way to populate a struct from a similar struct?

I am new to Rust, and am attempting to take a struct returned from a library (referred to as source struct) and convert it into protobuf message using prost. The goal is to take the source struct, map the source struct types to the protobuf message types (or rather, the appropriate types for prost-generated struct, referred to as message struct), and populate the message struct fields using fields from the source struct. The source struct fields are a subset of message struct fields. For example:
pub struct Message {
pub id: i32,
pub email: String,
pub name: String,
}
pub struct Source {
pub email: Email,
pub name: Name,
}
So, I would like to take fields from from Source, map the types to corresponding types in Message, and populate the fields of Message using Source (fields have the same name). Currently, I am manually assigning the values by creating a Message struct and doing something like message_struct.email = source_struct.email.to_string();. Except I have multiple Message structs based on protobuf, some having 20+ fields, so I'm really hoping to find a more efficient way of doing this.
If I understand you correctly you want to generate define new struct based on fields from another. In that case you have to use macros.
https://doc.rust-lang.org/book/ch19-06-macros.html
Also this question (and answer) could be useful for you Is it possible to generate a struct with a macro?
To convert struct values from one to another struct best way is to use From<T> or Into<T> trait.
https://doc.rust-lang.org/std/convert/trait.From.html
This is called FRU (functional record update).
This currently works only for structs with the same type and structs with the same type modulo generic parameters.
The RFC-2528 talks about a generalization that would make this work:
struct Foo {
field1: &'static str,
field2: i32,
}
struct Bar {
field1: f64,
field2: i32,
}
let foo = Foo { field1: "hi", field2: 1 };
let bar = Bar { field1: 3.14, ..foo };
Unfortunately, this has not yet been implemented.
You could make a method to create a Message from a Source like this.
impl Message {
pub fn from_source(source: &Source, id: i32) -> Self {
Message {
id: id,
email: source.email.to_string(),
name: source.name.to_string(),
}
}
}
And then,
let source = // todo
let id = // todo
let message = Message::from_source(&source, id);

is it possible to define a field use the keywords in rust

I am using rust to write a rest api, now the fluter client define the entity like this:
class WordDefinition {
String type;
String name;
List<String> values;
WordDefinition({
this.type,
this.name,
this.values,
});
}
the client use the type as a entity field name, in dart it works fine. But in the server side rust I could not define the field name like this, what should I do to avoid the rust keyword limit when define a entity? is it possible to define a entity name using type like this in rust:
use rocket::serde::Deserialize;
use rocket::serde::Serialize;
#[derive(Deserialize, Serialize)]
#[allow(non_snake_case)]
pub struct WordDefinition {
pub type: String,
pub text: String,
pub translations: Vec<String>,
}
impl Default for WordDefinition {
fn default() -> Self {
WordDefinition {
type: "".to_string(),
text: "".to_string(),
translations: vec![]
}
}
}
I define like this but obviously it could not work as expect in rust.
You can use "raw identifiers" by prefixing a keyword like: r#type.
You might also want to give it a different name in your Rust code, and use #[serde(rename)] to make it serialize with the name "type" like:
struct Foo {
#[serde(rename = "type")]
kind: String
}
Personally, I prefer the second way, because I find r#type a bit annoying to type and ugly, but that's just preference, there's not a "right" way

How to enforce that a string must not be empty in Rust?

what is the easiest way to enforce that a field in a struct must not be an empty string ("")?
Example:
struct User{
name: String,
otherAttribute: Option<String>
}
user1 = User{"".toString(), "".toString()} //--> this should throw an error
I know I can implement the "new" trait for User and enforce special rules there but for large structs this is not a lot of fun.
Simple, use this:
// in its own module
struct User {
name: String,
otherAttribute: Option <String>
}
impl User {
pub fn new (name: &str, ...) -> Self {
if name == "" {
panic! (""); // or return None/Err
}
...
}
}
This will make User uncreatable from outside of the module it is located in, therefore the caller is forced to use User::new which validates the string. It will also make the calling shorter.

How can I use serde to serialize a struct to another Rust data structure?

I have a data structure Document, which I would like to serialize other Rust structs to. It is basically a HashMap for the fields internally, however it interacts with a database API, so I will definitely want to convert other types into those Documents.
For example this struct
struct Entry {
id: String,
user: String,
duration: u32,
location: (f64, f64),
}
I already have a conversion to the Document type using the From trait, however this is an extra place I have to modify when the Entry struct changes. The implementation uses a DocumentBuilder and looks like this:
impl From<Entry> for Document {
fn from(entry: Entry) -> Self {
Document::builder()
.name(&entry.id) // set the name of the document
.field("user", entry.user) // add fields ...
.field("duration", entry.duration)
.field("location", entry.location)
.build() // build a Document
}
}
The field method can assign any value which can be converted to a FieldValue to a key. So the signature of field is:
impl DocumentBuilder {
// ...
pub fn field<T: Into<FieldValue>>(mut self, key: &str, value: T) -> Self { ... }
// ...
}
I would like to use serde and its derive feature to automatically serialize the struct and its fields into a Document. How would I go about doing this? I looked at the wiki for Implementing a Serializer but the example shown writes to a string and I would like to know how I can serialize to a data structure using the builder pattern.
The simplest way to do this would be to use serde_json::from_value (applicable even if you're not using JSON, but requires all fields to be valid JSON [e.g. no non-string keys in hashmaps]):
let entry = Entry {
a: 24,
b: 42,
c: "nice".to_string()
};
let v = serde_json::to_value(&entry).unwrap();
let document: Document = serde_json::from_value(v).unwrap();
Caveat: the value type of Document will have to implement Deserialize, and in a way that can deserialize any value into the correct argument. This can be done by using #[serde(untagged)], but may be prone to certain types being wrong, such as u8 being converted to u64.
Full playground example
A more sophisticated method that doesn't involve any unnecessary copies will require you to write a custom (De)serializer, and a good one to look at would be serde_transcode::transcode, which does the inverse of the thing you want - it converts between two data formats.

How to get struct fields and fields type in a compiler plugin?

I want to generate a HashMap which use struct fields as key, and use usize integer as value.
pub struct Article {
title: String,
content: String,
category: String,
comments: Vec<Comment>
}
pub struct Comment {
content: String
}
My expected output is:
{
title: 0,
content: 1,
category: 2
comments[].content: 3
}
My solution is impl my trait FieldsMapping for both Article and Comment:
pub trait FieldsMapping {
fn get_fields_map(&self) -> HashMap<String, usize>;
}
I want to write a compiler plugin for custom derive FieldsMapping.
How I get all fields within compiler plugin? And how can I know that fields type is Vec or other?
You don't.
Compiler plugins (i.e. procedural macros) are expanded before this information exists, so you can't access it. No, you can't delay expansion until types exist. No, if you turn it into a lint, you can't generate code, which then defeats the purpose of having a procedural macro.

Resources