I have around 10 structs with between 5-10 fields each and I want to be able to print them out using the same format.
Most of my structs look like this:
struct Example {
a: Option<String>,
b: Option<i64>,
c: Option<String>,
... etc
}
I would like to be able to define a impl for fmt::Display without having to enumerate the fields again so there is no chance for missing one if a new one is added.
For the struct:
let eg = Example{
a: Some("test".to_string),
b: Some(123),
c: None,
}
I would like the output format:
a: test
b: 123
c: -
I currently am using #[derive(Debug)] but I don't like that it prints out Some(X) and None and a few other things.
If I know that all the values inside my structs are Option<T: fmt::Display> can I generate myself a Display method without having to list the fields again?
This may not be the most minimal implementation, but you can derive serialisable and use the serde crate. Here's an example of a custom serialiser: https://serde.rs/impl-serializer.html
In your case it may be much simpler (you need only a handful of types and can panic/ignore on anything unexpected).
Another approach could be to write a macro and create your own lightweight serialisation solution.
I ended up solving this with a macro. While it is not ideal it does the job.
My macro currently looks like this:
macro_rules! MyDisplay {
($struct:ident {$( $field:ident:$type:ty ),*,}) => {
#[derive(Debug)]
pub struct $struct { pub $($field: $type),*}
impl fmt::Display for $struct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
$(
write!(f, "{}: {}\n",
stringify!($field).to_string(),
match &self.$field {
None => "-".to_string(),
Some(x) => format!("{:#?}", x)
}
)?;
)*
Ok(())
}
}
};
}
Which can be used like this:
MyDisplay! {
Example {
a: Option<String>,
b: Option<i64>,
c: Option<String>,
}
}
Playground with an example:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cc089f8aecaa04ce86f3f9e0307f8785
My macro is based on the one here https://stackoverflow.com/a/54177889/1355121 provided by Cerberus
Related
I'm attempting to use hcl-rs = 0.7.0 to parse some HCL. I'm just experimenting with arbitrary HCL, so I'm not looking to parse terraform specific code.
I'd like to be able to parse a block like this and get it's label as part of result
nested_block "nested_block_label" {
foo = 123
}
This currently doesn't work, but hopefully it shows my intention. Is something like this possible?
#[test]
fn deserialize_struct_with_label() {
#[derive(Deserialize, PartialEq, Debug)]
struct TestRoot {
nested_block: TestNested,
}
#[derive(Deserialize, PartialEq, Debug)]
struct TestNested {
label: String,
foo: u32,
}
let input = r#"
nested_block "nested_block_label" {
foo = 123
}"#;
let expected = TestRoot{ nested_block: TestNested { label: String::from("nested_block_label"), foo: 123 } };
assert_eq!(expected, from_str::<TestRoot>(input).unwrap());
}
Your problem is that hcl by default seems to interpret
nested_block "nested_block_label" {
foo = 123
}
as the following "serde structure":
"nested_block" -> {
"nested_block_label" -> {
"foo" -> 123
}
}
but for your Rust structs, it would have to be
"nested_block" -> {
"label" -> "nested_block_label"
"foo" -> 123
}
I'm not aware of any attributes that would allow you to bend the former into the latter.
As usual, when faced with this kind of situation, it is often easiest to first deserialize to a generic structure like hcl::Block and then convert to whatever struct you want manually. Disadvantage is that you'd have to do that for every struct separately.
You could, in theory, implement a generic deserialization function that wraps the Deserializer it receives and flattens the two-level structure you get into the one-level structure you want. But implementing Deserializers requires massive boilerplate. Possibly, somebody has done that before, but I'm not aware of any crate that would help you here, either.
As a sort of medium effort solution, you could have a special Labelled wrapper structure that always catches and flattens this intermediate level:
#[derive(Debug, PartialEq)]
struct Labelled<T> {
label: String,
t: T,
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Labelled<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct V<T>(std::marker::PhantomData<T>);
impl<'de, T: Deserialize<'de>> serde::de::Visitor<'de> for V<T> {
type Value = Labelled<T>;
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
if let (Some((label, t)), None) =
(map.next_entry()?, map.next_entry::<String, ()>()?)
{
Ok(Labelled { label, t })
} else {
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Other("Singleton map"),
&self,
))
}
}
}
deserializer.deserialize_map(V::<T>(Default::default()))
}
}
would be used like this:
#[derive(Deserialize, PartialEq, Debug)]
struct TestRoot {
nested_block: Labelled<TestNested>,
}
#[derive(Deserialize, PartialEq, Debug)]
struct TestNested {
foo: u32,
}
Lastly, there may be some trick like adding #[serde(rename = "$hcl::label")]. Other serialization libraries (e.g. quick-xml) have similar and allow marking fields as something special this way. hcl-rs does the same internally, but it's undocumented and I can't figure out from the source whether what you need is possible.
I'm trying to access an api, where I can specify what kind of fields I want included in the result. (for example "basic", "advanced", "irrelevant"
the Rust Struct to represent that would look something like
Values {
a: Option<String>;
b: Option<String>;
c: Option<String>;
d: Option<String>;
}
or probably better:
Values {
a: Option<Basic>; // With field a
b: Option<Advanced>; // With fields b,c
c: Option<Irrelevant>; // With field d
}
Using this is possible, but I'd love to reduce the handling of Option for the caller.
Is it possible to leverage the type system to simplify the usage? (Or any other way I'm not realizing?)
My idea was something in this direction, but I think that might not be possible with rust (at least without macros):
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=093bdf1853978af61443d547082576ca
struct Values {
a: Option<&'static str>,
b: Option<&'static str>,
c: Option<&'static str>,
}
trait ValueTraits{}
impl ValueTraits for dyn Basic{}
impl ValueTraits for dyn Advanced{}
impl ValueTraits for Values{}
trait Basic {
fn a(&self) -> &'static str;
}
trait Advanced {
fn b(&self) -> &'static str;
fn c(&self) -> &'static str;
}
impl Basic for Values {
fn a(&self) -> &'static str {
self.a.unwrap()
}
}
impl Advanced for Values {
fn b(&self) -> &'static str {
self.b.unwrap()
}
fn c(&self) -> &'static str {
self.c.unwrap()
}
}
//Something like this is probably not possible, as far as I understand Rust
fn get_values<T1, T2>() -> T1 + T2{
Values {
a: "A",
b: "B",
c: "C"
}
}
fn main() {
let values = get_values::<Basic, Advanced>();
println!("{}, {}, {}", values.a(), values.b(), values.c());
}
Clarifications (Edit)
The Values struct contains deserialized json data from the api I called. I can request groups of fields to be included in the response(1-n requested fields groups), the fields are of different types.
If I knew beforehand, which of those fields are returned, I wouldn't need them to be Option, but as the caller decides which fields are returned, the fields needs to be Option (either directly, or grouped by the field groups)
There are too many possible combinations to create a struct for each of those.
I fully realize that this cannot work, it was just "peudorust":
fn get_values<T1, T2>() -> T1 + T2{
Values {
a: "A",
b: "B",
c: "C"
}
}
But my thought process was:
In theory, I could request the field groups via generics, so I could create a "dynamic" type, that implements these traits, because I know which traits are requested.
The Traits are supposed to act like a "view" into the actual struct, because if they are requested beforehand, I know I should request them from the api to include them in the Struct.
My knowledge of generics and traits isn't enough to confidently say "this isn't possible at all" and I couldn't find a conclusive answer before I asked here.
Sorry for the initial question not being clear of what the actual issue was, I hope the clarification helps with that.
I can't quite gauge whether or not you want to be able to request and return fields of multiple different types from the question. But if all the information being returned is of a single type you could try using a HashMap:
use std::collections::HashMap;
fn get_values(fields: &[&'static str]) -> HashMap<&'static str, &'static str> {
let mut selected = HashMap::new();
for field in fields {
let val = match *field {
"a" => "Value of a",
"b" => "Value of b",
"c" => "Value of c",
// Skip requested fields that don't exist.
_ => continue,
};
selected.insert(*field, val);
}
selected
}
fn main() {
let fields = ["a","c"];
let values = get_values(&fields);
for (field, value) in values.iter() {
println!("`{}` = `{}`", field, value);
}
}
Additionally you've given me the impression that you haven't quite been able to form a relationship between generics and traits yet. I highly recommend reading over the book's "Generic Types, Traits, and Lifetimes" section.
The gist of it is that generics exist to generalize a function, struct, enum, or even a trait to any type, and traits are used to assign behaviour to a type. Traits cannot be passed as a generic parameter, because traits are not types, they are behaviours. Which is why doing: get_values::<Basic, Advanced>(); doesn't work. Basic and Advanced are both traits, not types.
If you want practice with generics try generalizing get_values so that it can accept any type which can be converted into an iterator that yields &'static strs.
Edit:
The clarification is appreciated. The approach you have in mind is possible, but I wouldn't recommend it because it's implementing it is extremely verbose and will panic the moment the format of the json you're parsing changes. Though if you really need to use traits for some reason you could try something like this:
// One possible construct returned to you.
struct All {
a: Option<i32>,
b: Option<i32>,
c: Option<i32>,
}
// A variation that only returned b and c
struct Bc {
b: Option<i32>,
c: Option<i32>,
}
// impl Advanced + Basic + Default for All {...}
// impl Advanced + Default for Bc {...}
fn get_bc<T: Advanced + Default>() -> T {
// Here you would set the fields on T.
Default::default()
}
fn get_all<T: Basic + Advanced + Default>() -> T {
Default::default()
}
fn main() {
// This isn't really useful unless you want to create multiple structs that
// are supposed to hold b and c but otherwise have different fields.
let bc = get_bc::<Bc>();
let all = get_all::<All>();
// Could also do something like:
let bc = get_bc::<All>();
// but that could get confusing.
}
I think the above is how you're looking to solve your problem. Though if you can, I would still recommend using a HashMap with a trait object like this:
use std::collections::HashMap;
use std::fmt::Debug;
// Here define the behaviour you need to interact with the data. In this case
// it's just ability to print to console.
trait Value: Debug {}
impl Value for &'static str {}
impl Value for i32 {}
impl<T: Debug> Value for Vec<T> {}
fn get_values(fields: &[&'static str]) -> HashMap<&'static str, Box<dyn Value>> {
let mut selected = HashMap::new();
for field in fields {
let val = match *field {
"a" => Box::new("Value of a") as Box<dyn Value>,
"b" => Box::new(2) as Box<dyn Value>,
"c" => Box::new(vec![1,3,5,7]) as Box<dyn Value>,
// Skip requested fields that don't exist.
_ => continue,
};
selected.insert(*field, val);
}
selected
}
fn main() {
let fields = ["a","c"];
let values = get_values(&fields);
for (field, value) in values.iter() {
println!("`{}` = `{:?}`", field, value);
}
}
I am trying to understand following enum from this repo
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct InitEscrowArgs {
pub data: EscrowReceive,
}
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct ExchangeArgs {
pub data: EscrowReceive,
}
#[derive(BorshSerialize, BorshDeserialize, Clone)]
pub enum EscrowInstruction {
InitEscrow(InitEscrowArgs),
Exchange(ExchangeArgs),
CancelEscrow(),
}
and it's use of it in this match from this repo.
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = EscrowInstruction::try_from_slice(instruction_data)?;
match instruction {
EscrowInstruction::InitEscrow(args) => {
msg!("Instruction: Init Escrow");
Self::process_init_escrow(program_id, accounts, args.data.amount)
}
EscrowInstruction::Exchange(args) => {
msg!("Instruction: Exchange Escrow");
Self::process_exchange(program_id, accounts, args.data.amount)
}
EscrowInstruction::CancelEscrow() => {
msg!("Instruction: Cancel Escrow");
Self::process_cancel(program_id, accounts)
}
}
}
I understand that this try_from_slice method gets some sort of byte array and deserialize it.
I do not understand how it determines which enum value to use.
The enum has 3 choices, InitEscrow / Exchange / CancelEscrow, but what determines the match to know which one it is suppose to select?
Seem to me the InitEscrowArgs and ExchangeArgs both takes in same struct. Both containing data that is EscrowReceive data type.
Method try_from_slice is part of the BorshDeserialize trait, which is derived on the enum in question. So, the choice between enum variants is made by the implementation of deserializer.
To see what is really going on, I've built the simplest possible example:
use borsh::BorshDeserialize;
#[derive(BorshDeserialize)]
enum Enum {
Variant1(u8),
Variant2,
}
By using cargo expand and a little manual cleanup, we can get the following equivalent code:
impl borsh::de::BorshDeserialize for Enum {
fn deserialize(buf: &mut &[u8]) -> Result<Self, std::io::Error> {
let variant_idx: u8 = borsh::BorshDeserialize::deserialize(buf)?;
let return_value = match variant_idx {
0u8 => Enum::Variant1(borsh::BorshDeserialize::deserialize(buf)?),
1u8 => Enum::Variant2,
_ => {
let msg = format!("Unexpected variant index: {}", variant_idx);
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
msg,
));
}
};
Ok(return_value)
}
}
Where the inner deserialize calls refers to impl BorshDeserialize for u8:
fn deserialize(buf: &mut &[u8]) -> Result<Self> {
if buf.is_empty() {
return Err(Error::new(
ErrorKind::InvalidInput,
ERROR_UNEXPECTED_LENGTH_OF_INPUT,
));
}
let res = buf[0];
*buf = &buf[1..];
Ok(res)
}
So, it works the following way:
Deserializer tries to pull one byte from input; if there's none - this is an error.
This byte is interpreted as an index of enum variant; if it doesn't match to one of variants - this is an error.
If the variant contains any data, deserializer tries to pull this data from the input; if it fails (according to the inner type's implementation) - this is an error.
I'm creating some User Input components in Rust. I'm trying to modularize these components using trait CustomInput. This trait implements some basic for all UI Input components, like being able to set and get the values of the component. All my UI components will take a String input, and parse that string to some typed value, maybe an f64, i32, or Path. Because different UI components can return different types, I created enum CustomOutputType, so that get_value can have a single return type for all components.
As users can input any random string, each UI component needs to have a means of ensuring that the input string can be successfully converted to the right type for the respective input component, I.e MyFloatInput needs to return a f64 value, MyIntInput needs to return an i32, and so on. I see that std::str::FromStr is implemented for most basic types, bool, f64, usize, but I don't think this kind of parsing is robust enough for UI inputs.
I'd like some more advanced functionality than std::str::FromStr provides, I'd like to be able to:
Evaluate expression, i.e input of 500/25 returns 20
Floor/Ceiling for min/max values: i.e if max is 1000 and input is 2000, value is 1000
What would be an idiomatic way of implementing such parsing
functionality for my UI components?
Is using the CustomOutputType enum an efficient strategy for handling the
different types the UI Components could return?
Would a parsing crate like Nom be advisable in this situation? I think this might be
overkill for what I'm doing, but I'm not sure.
I've taken a stab at creating such a system:
use std::str::FromStr;
use snafu::{Snafu};
#[derive(Debug, Snafu)]
pub enum Error{
#[snafu(display("Incorrect Type: {:?}", msg))]
IncorrectInputType{msg: String},
}
type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Clone, Debug)]
pub enum CustomOutputType{
CFloat(f64),
CInt(i32),
}
pub trait CustomInput{
fn get_value(&self)->CustomOutputType;
fn set_value(&mut self, val: String)->Result<()>;
}
pub struct MyFloatInput{
val: f64,
}
pub struct MyIntInput{
val: i32
}
impl CustomInput for MyFloatInput{
fn get_value(&self)->CustomOutputType{
CustomOutputType::CFloat(self.val)
}
fn set_value(&mut self, val: String)->Result<()>{
match f64::from_str(&val) {
Ok(inp)=>Ok(self.val = inp),
// Ok(inp)=>{self.val = inp}
Err(e) => IncorrectInputType { msg: "setting float input failed".to_string() }.fail()?
}
}
}
impl CustomInput for MyIntInput{
fn get_value(&self)->CustomOutputType{
CustomOutputType::CInt(self.val)
}
fn set_value(&mut self, val: String)->Result<()>{
match i32::from_str(&val) {
Ok(inp)=>Ok(self.val = inp),
Err(e) => IncorrectInputType { msg: "setting int input failed".to_string() }.fail()?
}
}
}
fn main() {
let possible_inputs = vec!["100".to_string(), "100.0".to_string(), "100/2".to_string(), ".1".to_string(), "teststr".to_string()];
let mut int_input = MyIntInput{val: 0};
let mut float_input = MyFloatInput{val: 0.0};
for x in &possible_inputs{
int_input.set_value(x.to_string()).unwrap();
float_input.set_value(x.to_string()).unwrap();
}
}
I want to write a macro that generates varying structs from an integer argument. For example, make_struct!(3) might generate something like this:
pub struct MyStruct3 {
field_0: u32,
field_1: u32,
field_2: u32
}
What's the best way to transform that "3" literal into a number that I can use to generate code? Should I be using macro_rules! or a proc-macro?
You need a procedural attribute macro and quite a bit of pipework. An example implementation is on Github; bear in mind that it is pretty rough around the edges, but works pretty nicely to start with.
The aim is to have the following:
#[derivefields(u32, "field", 3)]
struct MyStruct {
foo: u32
}
transpile to:
struct MyStruct {
pub field_0: u32,
pub field_1: u32,
pub field_2: u32,
foo: u32
}
To do this, first, we're going to establish a couple of things. We're going to need a struct to easily store and retrieve our arguments:
struct MacroInput {
pub field_type: syn::Type,
pub field_name: String,
pub field_count: u64
}
The rest is pipework:
impl Parse for MacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let field_type = input.parse::<syn::Type>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let field_name = input.parse::<syn::LitStr>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let count = input.parse::<syn::LitInt>()?;
Ok(MacroInput {
field_type: field_type,
field_name: field_name.value(),
field_count: count.base10_parse().unwrap()
})
}
}
This defines syn::Parse on our struct and allows us to use syn::parse_macro_input!() to easily parse our arguments.
#[proc_macro_attribute]
pub fn derivefields(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(attr as MacroInput);
let mut found_struct = false; // We actually need a struct
item.into_iter().map(|r| {
match &r {
&proc_macro::TokenTree::Ident(ref ident) if ident.to_string() == "struct" => { // react on keyword "struct" so we don't randomly modify non-structs
found_struct = true;
r
},
&proc_macro::TokenTree::Group(ref group) if group.delimiter() == proc_macro::Delimiter::Brace && found_struct == true => { // Opening brackets for the struct
let mut stream = proc_macro::TokenStream::new();
stream.extend((0..input.field_count).fold(vec![], |mut state:Vec<proc_macro::TokenStream>, i| {
let field_name_str = format!("{}_{}", input.field_name, i);
let field_name = Ident::new(&field_name_str, Span::call_site());
let field_type = input.field_type.clone();
state.push(quote!(pub #field_name: #field_type,
).into());
state
}).into_iter());
stream.extend(group.stream());
proc_macro::TokenTree::Group(
proc_macro::Group::new(
proc_macro::Delimiter::Brace,
stream
)
)
}
_ => r
}
}).collect()
}
The behavior of the modifier creates a new TokenStream and adds our fields first. This is extremely important; assume that the struct provided is struct Foo { bar: u8 }; appending last would cause a parse error due to a missing ,. Prepending allows us to not have to care about this, since a trailing comma in a struct is not a parse error.
Once we have this TokenStream, we successively extend() it with the generated tokens from quote::quote!(); this allows us to not have to build the token fragments ourselves. One gotcha is that the field name needs to be converted to an Ident (it gets quoted otherwise, which isn't something we want).
We then return this modified TokenStream as a TokenTree::Group to signify that this is indeed a block delimited by brackets.
In doing so, we also solved a few problems:
Since structs without named members (pub struct Foo(u32) for example) never actually have an opening bracket, this macro is a no-op for this
It will no-op any item that isn't a struct
It will also no-op structs without a member