3

I was under the impression that these two things were only semantically different.

However, this is possible:

struct Foo;

trait Bar<T> {
  fn resolve(&self) -> T;
}

impl Bar<isize> for Foo {
  fn resolve(&self) -> isize {
    return 0isize;
  }
}

impl Bar<usize> for Foo {
  fn resolve(&self) -> usize {
    return 1usize;
  }
}

#[test]
fn test_foo() {
  let foo = Foo;
  assert!((&foo as &Bar<isize>).resolve() == 0isize);
  assert!((&foo as &Bar<usize>).resolve() == 1usize);
}

While this is not:

struct Foo;

trait Bar {
  type T;
  fn resolve(&self) -> Self::T;
}

impl Bar for Foo {
  type T = isize;
  fn resolve(&self) -> isize {
    return 0isize;
  }
}

impl Bar for Foo {
  type T = usize;
  fn resolve(&self) -> usize {
    return 1usize;
  }
}

#[test]
fn test_foo() {
  let foo = Foo;
  assert!((&foo as &Bar<T = isize>).resolve() == 0isize);
  assert!((&foo as &Bar<T = usize>).resolve() == 1isize);
}

It generates:

<anon>:8:1: 13:2 error: conflicting implementations for trait `Bar` [E0119]
<anon>: 8 impl Bar for Foo {
<anon>: 9   type T = isize;
<anon>:10   fn resolve(&self) -> isize {
<anon>:11     return 0isize;
<anon>:12   }
<anon>:13 }

Am I missing something?

Is there a special syntax for what I'm trying to achieve, or is there really a... technical... distinction between a generic and an associated type?

Is there some circumstance in which an associated type has a tangible (rather than purely code prettiness) benefit over using a generic?

Doug
  • 32,844
  • 38
  • 166
  • 222
  • 1
    I think the book is pretty clear on this? https://doc.rust-lang.org/stable/book/associated-types.html The whole idea behind associated types is that an impl of the trait chooses the associated types. Like the `Add` trait doesn't allow you to implement adding two types twice to yield a different type for each add operation. It would be pretty surprising if adding two `i32`s yields a different result depending on the inferred result type – oli_obk Nov 11 '15 at 09:21
  • 4
    Well, you're right that generic type parameters and associated types are only semantically different. But the behavior you are observing in your example *is exactly this semantic difference*! It is not a technical difference - on the lowest level both associated types and type parameters are exactly the same. Associated types are "output" type parameters, while regular generic parameters are "input". – Vladimir Matveev Nov 11 '15 at 10:06
  • 4
    What is the "only" in "only semantically different" meant to mean? They're clearly syntactically different too, and anyway, aren't most pairs of language features (in any language) semantically different? (e.g. `if` has different semantics to `while`, and `fn` is different to `struct`, etc.) So it doesn't seem wildly surprising that these two different syntaxes mean different things; maybe the question you're really asking is when each one is appropriate ala http://stackoverflow.com/q/32059370/1256624 ? – huon Nov 11 '15 at 10:30
  • @VladimirMatveev is this the only difference between them? You're spot on with your comment if so; please post it as an answer so I can accept it. – Doug Nov 11 '15 at 11:29
  • @huon that's not my question at all; these are two different forms of generics; my understanding was that they were implemented identically, and were exactly equivalent except for the syntax; but this doesn't appear to be the case (it is possible to do something with one that it is not possible to do with the other). I'm totally ok with that; I just want to know exactly what the distinction between the two is. Is it just syntax? If so, that's a perfect answer to my question. – Doug Nov 11 '15 at 11:31
  • 2
    Ah, well, the answers to the question I linked go into the distinction. – huon Nov 11 '15 at 12:49

2 Answers2

5

I'll repeat my comment: it is true that type parameters and associated types are only semantically different. However, that's the main point why they are both present in the language - they do their own separate jobs, so it is not "just" semantic difference, it is the whole reason for their existence as a separate thing from type parameters.

Note that I do not even touch syntactic differences. Of course it is absolutely natural that there are syntactic differences. These are separate features after all; if they had no syntactic differences, then how you would distinguish between them? Their syntactic difference is closely tied to the semantic difference, because the way associated types are defined makes it clear that they have "output" position, compared to "input" position of type parameters, but technically both type parameters and associated types (and also the implicit Self, parameter, by the way) are the same thing.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
1

For anyone else who finds this question, there is also another technical distinction between type parameters and associated types as well.

If you attempt to implement a trait with an associated type you may see the error:

src/lib.rs:10:1: 15:2 error: the impl does not reference any types
defined in this crate; only traits defined in the current crate can
be implemented for arbitrary types [E0117]

If you have traits exported in a crate bar:

pub trait BarParam<TRtn> {
  fn bar() -> TRtn;
}

pub trait BarAssoc {
  type TRtn;
  fn bar() -> Self::TRtn;
}

You will find that a crate importing these traits will only be able to implement:

impl<'a> BarParam<Foo> for &'a str {
  fn bar() -> Foo {
    return Foo;
  }
}

While attempting to implement:

impl<'a> BarAssoc for &'a str {
  type TRtn = Foo;
  fn bar() -> Foo {
    return Foo;
  }
}

Will result in the error above.

Frankly I'm not really sure what's going on here, so if you do, by all means add a comment or another answer; but this is a tangible reason to avoid associated types when you're writing a crate.

Doug
  • 32,844
  • 38
  • 166
  • 222
  • 2
    I believe this is to do with Rust's "orphan rule": a trait implementation for Trait must be in the same crate as at least one of Trait, Ty1, or Ty2. This is so that it isn't possible for conflicting implementations on the same set of types to come from multiple upstream crates. Associated types aren't involved in looking up the unique implementation of a trait, so even if the associated type is in the current crate, there's nothing else preventing another crate from implementing the same trait, and hence the orphan rule applies. – Twisol Dec 11 '20 at 11:13