Reputation: 215
I am seeking to extend the pattern by utilizing default arguments in Rust as seen in previous question by someone which is using an "arguments" struct and the From
/Into
traits.
However, as you can see, the code clearly looks very redundant and adding associated functions to Foo
struct would require more code for the From
/Into
traits accordingly.
type FileName = Option<String>;
pub struct BarArg {
a: f64,
b: FileName,
}
impl Default for BarArg {
fn default() -> Self {
BarArg { a: 1.0, b: None }
}
}
impl From<()> for BarArg {
fn from(_: ()) -> Self {
Self::default()
}
}
impl From<f64> for BarArg {
fn from(a: f64) -> Self {
Self {
a,
..Self::default()
}
}
}
impl From<Option<String>> for BarArg {
fn from(b: Option<String>) -> Self {
Self {
b,
..Self::default()
}
}
}
impl From<(f64, Option<String>)> for BarArg {
fn from((a, b): (f64, Option<String>)) -> Self {
Self { a, b }
}
}
pub struct BazArg {
a: i32,
b: FileName,
}
impl Default for BazArg {
fn default() -> Self {
BazArg { a: 1, b: None }
}
}
impl From<()> for BazArg {
fn from(_: ()) -> Self {
Self::default()
}
}
impl From<i32> for BazArg {
fn from(a: i32) -> Self {
Self {
a,
..Self::default()
}
}
}
impl From<Option<String>> for BazArg {
fn from(b: Option<String>) -> Self {
Self {
b,
..Self::default()
}
}
}
impl From<(i32, Option<String>)> for BazArg {
fn from((a, b): (i32, Option<String>)) -> Self {
Self { a, b }
}
}
struct Foo;
impl Foo {
pub fn bar<A>(bar_arg: A)
where
A: Into<BarArg>,
{
match bar_arg.into().b {
Some(b) => {
println!("FileName is: {:?}", b);
}
None => {
println!("No FileName was passed");
}
};
}
pub fn baz<A>(baz_arg: A)
where
A: Into<BazArg>,
{
match baz_arg.into().b {
Some(b) => {
println!("FileName is: {:?}", b);
}
None => {
println!("No FileName was passed");
}
};
}
}
fn main() {
Foo::bar(());
Foo::bar(Some("bar.toml".to_string()));
Foo::baz(());
Foo::baz(Some("baz.toml".to_string()));
}
Is there any way to somehow implement these codes in a non-redundant and concise manner?
Upvotes: 0
Views: 178
Reputation: 71005
Don't. Do not emulate features, especially when they have good (even better) alternatives. And do not abuse other features to emulate those features.
That being said, it is possible to make it less boilerplate-y using macros. An important point to note is: what if two parameters have the same type? this will generate conflicting From
implementations. You have to make sure it either doesn't happen or you only allow omitting the last parameters. You can expose the parameters struct to allow more flexible filling.
The tricky part is that both for allowing all consecutive parameter sequences and for allowing only the last we will need TT munching. This is because macro_rules!
cannot extract from the middle of a list, or even from its end. It can only match its start.
Here's a macro that only allows omitting the last arguments. It also always requires you to pass a tuple, so you pass (1,)
instead of just 1
, for example. It is pretty easy to allow this, but I like the always-tuple approach because I feel it is more consistent (lifting the only-last limit is much harder, but possible):
macro_rules! impl_from_for_default {
// Recursion stop condition
{
$struct_name:ident {}
} => {
impl ::core::convert::From<()> for $struct_name {
fn from((): ()) -> Self {
Self::default()
}
}
};
{
$struct_name:ident {
$($param:ident : $param_ty:ty,)*
}
} => {
impl ::core::convert::From<( $($param_ty,)* )> for $struct_name {
fn from(( $($param,)* ): ( $($param_ty,)* )) -> Self {
Self {
$($param,)*
..Self::default()
}
}
}
omit_last_param! {
$struct_name
[ ]
$($param : $param_ty,)*
}
};
}
macro_rules! omit_last_param {
// Recursion stop condition
{
$struct_name:ident
[ $($first_params:tt)* ]
$last_param:ident : $last_param_ty:ty,
} => {
impl_from_for_default! {
$struct_name {
$($first_params)*
}
}
};
{
$struct_name:ident
[ $($first_params:tt)* ]
$param:ident : $param_ty:ty,
$($rest:tt)*
} => {
omit_last_param! {
$struct_name
[
$($first_params)*
$param : $param_ty,
]
$($rest)*
}
};
}
macro_rules! with_default {
{
$vis:vis $struct_name:ident {
$($param:ident : $param_ty:ty = $param_default:expr),* $(,)?
}
} => {
$vis struct $struct_name {
$($param : $param_ty,)*
}
impl ::core::default::Default for $struct_name {
fn default() -> Self {
Self {
$($param : $param_default,)*
}
}
}
impl_from_for_default! {
$struct_name {
$($param : $param_ty,)*
}
}
};
}
Example usage:
with_default! {
pub BazArg {
a: i32 = 1,
b: FileName = None,
}
}
Upvotes: 3