Re:Monads and GATs in Nightly Rust
by brundolf on Friday December 11, 2020 @04:36PM
(#25391363)
> Rust uses "traits" to do both generic-like things and inheritance-like things. It's not clear this was a win. The result seems to be a system which does both badly.
IMO the fact that Rust doesn't do inheritance well is a feature, not a bug. My understanding is that most people even in OOP circles have realized that composition is better than inheritance, and I see Rust's choices around it as a reflection of that.
> It's not enough that every operation on a type needed by a generic be implemented. The type has to be part of a single trait that provides for all those operations.
Maybe I'm misunderstanding, but do you know that a generic can combine multiple traits? For example:
fn foo<T: Copy + Add + Sync>(x: T) {
...
}
Here T must implement Copy and Add and Sync.
Maybe what you're talking about is the fact that methods' identities aren't fully described by their names + types, but also by their trait? i.e.:
trait TraitA {
fn foo(&self, x: i32) -> i32;
}
trait TraitB {
fn foo(&self, x: i32) -> i32;
}
struct Foo {
}
impl TraitA for Foo {
fn foo(&self, x: i32) -> i32 {
x + 1
}
}
fn requires_b<T: TraitB>(x: T) {
x.foo(12);
}
fn main() {
let f = Foo { };
requires_b(f);
}
error[E0277]: the trait bound `Foo: TraitB` is not satisfied
--> src/main.rs:25:16
|
19 | fn requires_b<T: TraitB>(x: T) {
| ------ required by this bound in `requires_b`
...
25 | requires_b(f);
| ^ the trait `TraitB` is not implemented for `Foo`
This was an opinionated decision made by the Rust team - which I think was a good decision - to address the fact that it's possible to have unwanted overlaps between trait method signatures. Just because fn foo(&self, x: i32) -> i32 is defined for a struct, doesn't mean it's the same foo that you're wanting to call. Distinguishing things by trait strengthens the contract. It also avoids the diamond problem. You're right that this isn't directly related to "safety" in a memory sense, but it's part of an overarching philosophy of Rust that encourages intentionality and discourages footguns.
> Generic parameters can only be types, not numbers.
IMO the fact that Rust doesn't do inheritance well is a feature, not a bug. My understanding is that most people even in OOP circles have realized that composition is better than inheritance, and I see Rust's choices around it as a reflection of that.
> It's not enough that every operation on a type needed by a generic be implemented. The type has to be part of a single trait that provides for all those operations.
Maybe I'm misunderstanding, but do you know that a generic can combine multiple traits? For example:
Here T must implement Copy and Add and Sync.Maybe what you're talking about is the fact that methods' identities aren't fully described by their names + types, but also by their trait? i.e.:
This was an opinionated decision made by the Rust team - which I think was a good decision - to address the fact that it's possible to have unwanted overlaps between trait method signatures. Just because fn foo(&self, x: i32) -> i32 is defined for a struct, doesn't mean it's the same foo that you're wanting to call. Distinguishing things by trait strengthens the contract. It also avoids the diamond problem. You're right that this isn't directly related to "safety" in a memory sense, but it's part of an overarching philosophy of Rust that encourages intentionality and discourages footguns.> Generic parameters can only be types, not numbers.
This is a work in progress and has partially been rolled-out; the full implementation is on the way: https://rust-lang.github.io/rfcs/2000-const-generics.html