4 ms·
This. Traits and macros are two real problems with rust. Orphan rule is one, but also const, async and unnamable types (mostly closures). These barely work with
by zk4x 2y ago
This. Traits and macros are two real problems with rust. Orphan rule is one, but also const, async and unnamable types (mostly closures). These barely work with traits or do not work at all. If rust did not have closures, it'd be a lot simpler to solve these. Is it so hard to just create a normal function instead of closure?
Perhaps we need to go back to the basics a bit? What is a trait?
1. A set of functions, associated types and generic types
2. A marker/tag (e.g. Send, Sync)
Orphan rules do not seem to be problem for marker traits. Library authors must be responsible for enforcing whether their types are Send/Sync, etc or not.
As for normal traits, it's too late for rust, but I'd just limit traits to being only sets of function definitions, e.g.
trait Iterator = fn next<T>(&mut self) -> Option<T> + fn len(&self) -> usize
Then adding set operations (and, or, xor, not) for traits would be pretty easy, keeping most of power for defining generics.
More importantly traits could be just aliases and two traits with the same set of functions would be equal. This solves orphan problem - you would not need to import or export traits, it would be just normal resolution of functions. Do I call this function from crate A, or crate B? That's a solved problem.
- dgfitz 2y ago> trait Iterator = fn next<T>(&mut self) -> Option<T> + fn len(&self) -> usize This is why I don’t have interest in learning rust. When I see people write c++ code like this I wonder why they feel the need to be so clever. That is an abysmal thing to maintain.
- steveklabnik 2y agoThat isn’t real Rust code, though it’s made up of valid bits of Rust syntax.
- dgfitz 2y agoOh, ok that’s fair. I guess I should at least learn the grammar better before commenting more on rust.
- moomin 2y agoI mean, C#'s IEnumerator is equivalent and is way more complex. You just don't need to think about it much.
- marcosdumay 2y agoNope, just because you lack familiarity with the symbols, it doesn't make that declaration complex. It's not even "clever". It's completely plain, and the bare minimum and obvious definition of what an "iterator" should be.
- pyrale 2y agoThe hard problem is not sharing the trait, but sharing the trait instance. With your solution, if too modules define traits with identical type signatures but different implementations, it would be impossible for the compiler to decide which impl to use.
- zk4x 2y agoIf there are two modules module_a and module_b, and each defines a function called foo, how does the compiler decide which foo should be used? It just checks whether you imported module_a::foo or module_b::foo.
- pyrale 2y agoThe instance could be defined in module c.
- zk4x 2y agoPerhaps I should have been more clear. The point is you would not implement traits. You would just implement functions. You would not implemet traits, you would just write functions iter and len for your type. When calling a function, compiler would check separately for existence of each function defined in the trait. That is a trait would be just like any other type alias so that you do not need to repeating complex function names everywhere: You could write: trait Iterator = fn next<T>(&mut self) -> Option<T> + fn len(&self) -> usize fn filter(iter: impl Iterator) but that would be just syntactic sugar for this: fn filter(iter: fn next<T>(&mut self) -> Option<T> + fn len(&self) -> usize) Basically removing traits alltogether, just relying on functions.
- pyrale 2y agoSo you define your filter function by saying it can use anything that has the next and the len functions. Cool. If a type A defined in module_a doesn’t have the functions defined in its module, should it still be filterable? If the required function is defined in a module_b should A be filterable?