Write a struct with variable content as the field name - rust

I need to write a bunch of struct with similar name within it. Such as:
pub struct ContactUpdate {
pub full_name: String,
pub full_address: String,
/// .... many other fields
}
pub struct Contact {
pub contact_id: Option<ObjectId>,
pub full_name: String,
pub full_address: String,
pub created_at: DateTime
/// .... many other fields
}
/// ... other structs with similar content/field name
I'm lazy. So instead of hard-coding each field name by hand, I think how can I make the field name of the struct into contants with fewer characters so I don't have to type as much. Also with other benefits to export that constants and use it in other files that need it.
pub const ID: &str = "contact_id";
pub const NAME: &str = "full_name";
pub const TIME: &str = "created_at";
pub const ADDR: &str = "full_address";
pub struct Contact {
pub ID: Option<ObjectId>,
pub NAME: String,
pub ADDR: String,
pub TIME: DateTime
/// .... many other fields
}
pub struct ContactUpdate {
pub NAME: String,
pub ADDR: String,
/// .... many other fields
}
Is this possible?

No. It is impossible.
If you really want (don't!) you can have a macro for that.
However, the entire reason for the existence of field names is for the programmers to know what they mean. If you want to use them as constants, you just give them no meaning and can get rid of them completely. At that time, you're back to the old days where variable name lengths were limited to 7-8 characters and people used all sorts of bad and unreadable codes to work with that. Don't do that.

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 do I read attributes programatically in Rust

I'd like to read attributes programatically. For example, I have a struct which has attributes attached to each field:
#[derive(Clone, Debug, PartialEq, Message)]
pub struct Person {
#[prost(string, tag="1")]
pub name: String,
/// Unique ID number for this person.
#[prost(int32, tag="2")]
pub id: i32,
#[prost(string, tag="3")]
pub email: String,
#[prost(message, repeated, tag="4")]
pub phones: Vec<person::PhoneNumber>,
}
(source)
I'd like to find the tag associated with the email field.
I expect there is some code like this to get the tag at runtime:
let tag = Person::email::prost::tag;
Since attributes are read only at compile time you need to write a procedural macro to solve such issue.
You can find information with following designators in your macro.
Field name with ident
Attribute contents with meta
After you find your field name and your meta, then you can match the stringified result with given input parameter in macro like following:
macro_rules! my_macro {
(struct $name:ident {
$(#[$field_attribute:meta] $field_name:ident: $field_type:ty,)*
}) => {
struct $name {
$(#[$field_attribute] $field_name: $field_type,)*
}
impl $name {
fn get_field_attribute(field_name_prm : &str) -> &'static str {
let fields = vec![$(stringify!($field_name,$field_attribute)),*];
let mut ret_val = "Field Not Found";
fields.iter().for_each(|field_str| {
let parts : Vec<&str> = field_str.split(' ').collect();
if parts[0] == field_name_prm{
ret_val = parts[2];
}
});
ret_val
}
}
}
}
my_macro! {
struct S {
#[serde(default)]
field1: String,
#[serde(default)]
field2: String,
}
}
Please note that it assumes that every field in the struct has an attribute. And every field declaration is ending with , including last field. But with some modification on regex you can make it available for optional attributes as well.
Here working solution in Playground
For further info about designators here is the reference
Also you can take a quick look for procedural macros here

How to restrict the construction of struct?

Is it possible to forbid creating an instances directly from member initialization?
e.g.
pub struct Person {
name: String,
age: u8,
}
impl Person {
pub fn new(age: u8, name: String) -> Person {
if age < 18 {
panic!("Can not create instance");
}
Person { age, name }
}
}
I can still use
Person {
age: 6,
name: String::from("mike")
}
to create instance. Is there anyway to avoid this?
Answer to question
You cannot create that struct from member initialization, because members are by default private and cannot be used directly. Only the immediate module and its submodules can access private fields, functions, ... (see the book about visibility).
Your example works, because your function is in that certain scope.
mod foo {
pub struct Person {
name: String,
age: u8,
}
impl Person {
pub fn new(age: u8, name: String) -> Person {
if age < 18 {
panic!("Can not create instance");
}
Person { age, name }
}
}
}
use foo::Person; // imagine foo is an external crate
fn main() {
let p = Person {
name: String::from("Peter"),
age: 8,
};
}
(Playground)
error[E0451]: field `name` of struct `Person` is private
error[E0451]: field `age` of struct `Person` is private
Make it possible to create a struct from the outside
On the other hand, if you want to make it possible to create an instance by member initialization, use the pub keyword in front of all members.
pub struct Person {
pub name: String,
pub age: u8,
}
Make it possible to access the fields, but not creating a struct from the outside
Please see KittenOverflows answer for a better approach to this.
--
Sometimes it's useful to let the user of your crate access the members directly, but you want to restrict the creation of an instance to your "constructors". Just add a private field.
pub struct Person {
pub name: String,
pub age: u8,
_private: ()
}
Because you cannot access _private, you cannot create an instance of Person directly.
Also the _private field prevents creating a struct via the update syntax, so this fails:
/// same code from above
fn main() {
let p = Person::new(8, String::from("Peter"));
let p2 = Person { age: 10, ..p };
}
error[E0451]: field `_private` of struct `foo::Person` is private
--> src/main.rs:27:34
|
27 | let p2 = Person { age: 10, ..p };
| ^ field `_private` is private
For Rust >= 1.40.0, consider applying the non_exhaustive attribute to your struct.
// Callers from outside my crate can't directly construct me
// or exhaustively match on my fields!
#[non_exhaustive]
pub struct Settings {
pub smarf: i32,
pub narf: i32,
}
More info in the 1.40.0 release notes.

Multiple return types from a method

I am trying to write a simple TV-episode file renamer in Rust.
A filename is parsed, and might be one of several types (date-based, season/episode-number based etc). This parsed file is then turned into a "populated file" with data from a database (which is then formatted into a new filename)
Initially I tried having the parse method take a filename and return an enum variant:
enum ParsedFile{
DateBased{series: String, date: Date},
SeasonBased{series: String, season: i32, episode: i32},
// etc
}
fn parse(fname:&str) -> Option<ParsedFile>{
...
}
This worked fine, however the methods to take the ParsedFile and do different things for each episode became messy
For example, to separate the ParsedFile->PopulatedFile translation into separate methods, I have to match the variants, then destructure this in the method
struct PopulatedFile {
seriesname: String,
season: i32,
episode: i32,
episodename: String,
airdate: Date,
}
fn _populate_seasonbased(file: ParsedFile) -> Result<PopulatedFile, TvdbError>{
// can't just access file.season or file.episode, have to destructure enum again
}
fn populate(f: ParsedFile) -> Result<PopulatedFile, TvdbError> {
return match f {
ParsedFile::DateBased{..} =>
_populate_datebased(f),
// ...
}
}
This seemed really clunky, and I'm sure there must be a better way.
It would be nicer to have each episode type as a separate struct, e.g:
struct DateBased{
series: String,
date: Date
}
struct SeasonBased{
series: String,
season: i32,
episode: i32
}
..then I could, say, implement a ToPopulatedFile trait for each episode type. However I couldn't find a way to write the parse method in this example (i.e write a method which might return a DateBased struct or a SeasonBased struct)
Is there a good way to structure this?
You could design a solution around trait, but it would probably be much more work than simply adapting your current solution slightly:
struct DateBased{
series: String,
date: Date
}
struct SeasonBased{
series: String,
season: i32,
episode: i32
}
enum ParsedFile{
Date(DateBased),
Season(SeasonBased),
// etc
}
fn _populate_datebased(file: DateBased) -> Result<PopulatedFile, TvdbError>;
fn _populate_seasonbased(file: SeasonBased) -> Result<PopulatedFile, TvdbError>;
fn populate(f: ParsedFile) -> Result<PopulatedFile, TvdbError> {
return match f {
ParsedFile::Date(d) => _populate_datebased(d),
// ...
}
}
You can combine enum and struct in Rust, and I personally find it worth it to put name on things specifically because it allows manipulating them more easily.

Resources