-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve boilerplate for Trait impl #2676
Comments
I believe a large part of this is covered by #2089. |
@lxrec, AFAIK #2089 deals only with implied bounds? This doesn't have much to do with them, and hence will likely not have the same issues as the Since, everything here is limited in the block the scope, it's equivalent to the compiler simply inheriting the traits from the parent block. This is a simpler and tightly scoped approach that includes removing boilerplate with generic parameters and lifetimes as well. #2089 will still require |
A priori, I'd worry removing too many I think #2089 covers all significant use cases if implied bounds come from type aliases. I'm unsure if implied bounds coming from type aliases creates other problems though, but if not then the type alias solutions sounds far more powerful and less strange than this bespoke syntax. I also think this strays into parameterized module land since
Again parameterized modules are way more powerful, so if we want them eventually then we should favor them over this syntax. Also, any issues with parameterized modules might indicate issues here too, meaning that discussion must be revisited carefully. I suppose implied bounds coming from type aliases might fit with the 2019 priorities, but parameterized modules sound pretty far afield. |
This proposal only removes them inside a type impl. And the type becomes the place to define, which I think is appropriate. I'd think that's concise and to the point. I'm rather convinced to take a view that differs from yours here as any reasonably complex project is just filled with
This, I personally am not a fan of - I think this does what you say - Harm explicitness and create confusion. |
Citation needed. Or, I just don't see how specifying the type constraints for an impl counts as "syntax getting out of hand". Furthermore, the proposed syntax unconditionally increases indentation by one level – and makes trait methods stand on a different indent level than inherent methods. This is at best ugly, and makes method definition syntax inconsistent. The third problem with this feature is that it's severely non-orthogonal: I'd very much not like two very similar pieces of syntax for the exact same semantic purpose. |
What about just making it implicit?struct Services<'a, T: SomeTrait> { wrap_drive: &'a WarpDrive<T> };
impl Services<__> {
}
impl Clone for Services<__> {
fn clone(&self) -> Services<__> {
Services {
// ..
}
}
} It's because rust could determine the types, and I would definitely don't want to change every occurrence of the 'Services' whenever it receives a new type. When I want to be more specific, I could do it. E.g.: impl Clone for Services<_, Something> {
fn clone(&self) -> Services<_> {
Services {
// ..
}
}
} There would be two new language feature:
Usually, we want to write testable code.Currently, I can write easily with dynamic dispatch because those do not require template arguments. pub struct Game {
map: Box<dyn Map>,
character: Box<dyn Character>,
food: Box<dyn Food>,
/// .... some other members
}
impl Game {
// ...
} Whereas in static dispatch case: pub struct Game<
TSomething: Something,
TMap: Map<TSomething>,
TCharacter: Character<TSomething>,
TFood: Food<TSomething>,
> {
map: TMap,
character: TCharacter,
food: TFood,
}
impl<
TSomething: Something,
TMap: Map<TSomething>,
TCharacter: Character<TSomething>,
TFood: Food<TSomething>,
> Game<TSomething, TMap, TCharacter, TFood> {
// ...
} So I would make this instead: pub struct Game<
TSomething: Something,
TMap: Map<TSomething>,
TCharacter: Character<TSomething>,
TFood: Food<TSomething>,
> {
map: TMap,
character: TCharacter,
food: TFood,
}
impl Game<__> {
// ...
} |
It looks like you are trying to put the trait bounds on the type definition and infer them from there in the Besides, if you need to repeat the trait bounds, you can just copy and paste. You aren't forced to power-type everything twice. |
That's exactly what I want, but please let's focus on this proposal.
Interoperability is not decreasing. Imagine it as a preprocessor that includes the types before the build.
This would constrain all types very well defined.
Why?
Code duplication is generally a bad idea. Software constantly changes, and if it's hard to do, I would call them legacy. |
You can look it up on URLO. That's the entire point that I was making w.r.t. interoperability, and that you have missed, apparently. Edit: Official API guidelines, the related issue, a Reddit thread discussing the same |
It'd become easier to teach that if clippy warned against bounds on data structures that violate those rules (or perhaps some superset like non-associated type bound on a |
I have just read these and some others about the topic. Let's just have a glimpse of other languages: class UserController {
repo: UserRepositoryInterface;
construct(repo: UserRepositoryInterface) {
this.repo = repo;
}
index(): Promise<User[]> {
return this.repo.getAll();
}
}
// test:
class FakeUserRepository implements UserRepositoryInterface {
async getAll(): Promise<User[]> {
return [new User("Foo")];
}
}
let c = new UserController(new FakeUserRepository);
assert.equal(c.index().length, 1);
assert.equal(c.index()[0].name, "Foo"); Simple and straightforward to use. What about this one?:
In this case, everything is solved. Inferring the types: Game {
map: RectMap::new(100, 100),
character: Snake::new(),
food: Apple::new(),
} EDIT: When the only reason to make it abstract is to test it, I don't need template arguments. |
The "other language" also does it with dynamic dispatch. Then so can you. It is not the case that dynamic dispatch makes the types go away. In the case of Your example with |
I'm running into this annoyance. The boilerplate starts getting out of hand when you have a custom type (which takes a type parameter) and start implementing multiple traits for it where you just want to reuse the same bounds for the type parameter. Perhaps something like associated type declarations on inherent impls could help this situation? Or a way to express bounds on an inherent impl associated type? If any of that is even possible or makes sense. Idk. In the meantime, I suppose it's possible to define a macro for the type parameter/bound boilerplate. |
Currently impl for a struct and traits look like this:
Now if we add lifetimes, and/or generics, the syntax gets out of hand rather quickly.
What I'm proposing is to be able to write this instead:
Advantages:
impl
when it's an in-module struct.Disadvantages:
The text was updated successfully, but these errors were encountered: