Proposal
I find impl StructName
syntax quite repetitive, especially when the struct has generics with bound.
struct MyStruct<A, B: Foo, C: Bar, D: Baz<A, B, C>> { /* ... */ }
impl<A, B: Foo, C: Bar, D: Baz<A, B, C>> MyStruct<A, B, C, D> {
pub fn method(/* ... */) { /* ... */ }
}
impl<A, B: Foo, C: Bar, D: Baz<A, B, C>> Trait for MyStruct<A, B, C, D> { /* ... */ }
I propose a cleaner syntax:
struct MyStruct<A, B: Foo, C: Bar, D: Baz<A, B, C>> {
/* fields */
impl Self {
pub fn method(/* ... */) { /* ... */ }
}
impl MyTrait for Self { /* ... */ }
}
or even shorter:
struct MyStruct<A, B: Foo, C: Bar, D: Baz<A, B, C>> {
/* fields */
pub fn method(/* ... */) { /* ... */ }
}
// this variant lefts out trait impls
impl<A, B: Foo, C: Bar, D: Baz<A, B, C>> Trait for MyStruct<A, B, C, D> { /* ... */ }
I quite like @CAD97's suggestion as well:
for <A, B: Foo, C: Bar, D: Baz<A, B, C>> {
struct MyStruct { /* ... */ }
impl MyStruct {
pub fn method(/* ... */) { /* ... */ }
}
impl Trait for MyStruct { /* ... */ }
}
Motivating Example
This is a real code in an application that I am working on:
use super::super::{data::TagMapIndex, sizes::sidebar::*, style, utils::Callable};
use super::IndentedButton;
use iced::*;
use std::collections::BTreeMap;
#[derive(Debug, Default, Clone)]
pub struct Controls(pub BTreeMap<TagMapIndex, button::State>);
pub struct TagList<'a, Theme, GetContent, GetMessage, GetActivated> {
pub controls: &'a mut Controls,
pub button_prefix: &'a str,
pub get_content: GetContent,
pub get_message: GetMessage,
pub get_activated: GetActivated,
pub theme: Theme,
}
impl<'a, Theme, Message, GetContent, GetMessage, GetActivated> TagList<'a, Theme, GetContent, GetMessage, GetActivated>
where
Message: Clone + 'a,
Theme: style::Theme + Copy,
GetContent: Callable<Input = TagMapIndex, Output = Element<'a, Message>> + Clone,
GetMessage: Callable<Input = TagMapIndex, Output = Message> + Clone,
GetActivated: Callable<Input = TagMapIndex, Output = bool> + Clone,
{
fn into_element(self) -> Element<'a, Message> {
let TagList {
controls,
button_prefix,
get_activated,
get_message,
get_content,
theme,
} = self;
let mut button_list = Column::new();
for (index, state) in controls.0.iter_mut() {
let index = *index;
let activated = get_activated.clone().call(index);
let button: Button<'a, Message> = IndentedButton {
prefix: if activated { button_prefix } else { "" },
content: get_content.clone().call(index),
state,
}
.into_button()
.width(Length::Units(SIDEBAR_LENGTH))
.on_press(get_message.clone().call(index))
.style(style::BinaryStateButton {
style: theme.style(),
activated,
});
button_list = button_list.push(button);
}
button_list.into()
}
}
#[derive(Debug, Copy, Clone)]
struct GetStyle<GetActivated, Theme> {
get_activated: GetActivated,
theme: Theme,
}
impl<'a, GetActivated, Theme> Callable for GetStyle<GetActivated, Theme>
where
GetActivated: Callable<Input = TagMapIndex, Output = bool>,
Theme: style::Theme,
{
type Input = TagMapIndex;
type Output = style::BinaryStateButton;
fn call(self, x: Self::Input) -> Self::Output {
style::BinaryStateButton {
activated: self.get_activated.call(x),
style: self.theme.style(),
}
}
}
Originally, it was a function create_tag_list(theme: impl style::Theme + Copy, ...)
, the problem is:
- I must call
create_tag_list
with the right order of arguments, but there are too many arguments making the code ambiguous. - Named function parameter would solve the aforementioned problem, however, it does not assign meaning to a set of named parameters the same way a struct does. I also want to reuse the same set of named parameters in different functions, a struct is better for that.
So I chose to create a struct. Other problems arise:
- I still must ensure correct order of generic parameters when writing
impl
. - Using trait with associate types would solve aforementioned problem, but:
- It would require even more boilerplate when I use the struct in another
impl
. - I still have to specify bound trait.
- Type inference would stop working. Constructing the struct would require explicit type parameters that impls the utility trait.
- It would require even more boilerplate when I use the struct in another
- Speaking of bound traits, when they are not satisfied, resulting error messages are numerous and confusing (not unlike that of a C++ template).
- Specifying trait bound in the struct declaration would have resulted in a less confusing error messages in the right place. The only reason I did not do that is because a) I dislike boilerplate more than I dislike confusing error messages; and b) It would only work if the trait bounds in the struct are exactly the same as the trait bounds in the impl.
- Thankfully, I did not so eagerly attempt to use trait with associate types, none can imagine what the error messages would be.
- And of course, repetitive generic parameters in the struct and in the impl.