While writing code with Rust, you may run into a situation where you need to declare a new Generic Type in the middle of everything. In this case, you will need to fix a substantial amount of unrelated code as well.
However, if the type I want to add can be represented as an AssociatedType, I can reduce unnecessary code modifications. An example is shown below, where the from_str function returns Self upon success and String upon failure.
The example above was decent; however, I wanted to express an Error type, which would hold more variety of information than String. In order to solve this problem, I’ve tried the method as shown above.
However, if you use AssociatedTypes, you do not need to modify the code of a function that have no relation to Err such as do_great_work. This trait requires the Address type in order to find the correct value in the cache.
If we are sure that a type T implements Petites e.g. in generics, we could simply use T::First to obtain the associated type. Compared to other languages I've learned, Rust has a fair few concepts that can be a bit tricky to get your head around.
Borrowing, ownership, and the borrow-checker are common enough to have spawned a range of memes on their own, and I've personally spent hours on lifetime issues only to give up and rewrite something using cloning. What really never stuck was how it was different from generics, and why you'd need (or event want) associated types.
The quick and dirty answer to when to use generics and when to use associated types is: Use generics if it makes sense to have multiple implementations of a trait for a specific type (such as the Form
If, after reading this post, you're still not sure what I'm on about, check out the of the Book, and specifically the section on. Finally, this post assumes you have some familiarity with programming (and with Rust specifically), and with some forms of generic programming.
We'll see examples of this later, so don't worry if it's a bit fuzzy right now. Much like with generics, you can put constraints on the type if you want to, or you can choose not to.
The Iterator trait has an associated type Item and a function next. If we want to set constraints on the associated type or type parameter, we use the same syntax as Rust uses everywhere else for bounds: the : operator.
From what I tried, it seems you cannot put the default type (= String) in the where clause of the trait. Judging from the GitHub tracking issue, this is being worked on but I've not seen anything about stabilization of this yet.
Now that we've covered what they are and what the syntax looks like, let's continue by looking at what they have in common. Even if the notation is a bit different, anywhere you have an associated type, you can replace it with a generic type instead (though the opposite does not hold true).
As the RFC puts it: associated types do not increase the expressiveness of traits per se, because you can always use extra type parameters to a trait instead”. As we move into the next section, we'll examine what makes them different, and why you'd want to choose one over the other.
As we've seen, generics and associated types cover a lot of the same use cases, but there are some reasons why you might choose one over the other. The Form
This makes generics very useful if it makes sense to have multiple trait implementations varying only in type parameters. In short, use generics when you want to a type A to be able to implement a trait any number of times for different type parameters, such as in the case of the Form
If you want to know more about associated types and what problems they solve, I recommend starting with the RFC that introduced them and the . The, which uses both generics (with defaults) and associated types, is also well worth a read.
Disregarding any implementation/performance strategy, both excerpts above allow the user to specify in a dynamic manner how hello_world should behave. Similarly to the trait methods above, an associated type is a form of late binding (though it occurs at compilation), allowing the user of the trait to specify for a given instance which type to substitute.
Nonetheless, the author exercised their judgment and decided that overloading the return type for additions was unnecessary. Still, beyond the uni city argument, I would mention that associated types make using the trait easier as they decrease the number of parameters that have to be specified, so in case the benefits of the flexibility of using a regular trait parameter are not obvious, I suggest starting with an associated type.
Like functions, associated constants work with traits and ends as well. With a trait, you can use an associated constant in the same way you’d use an associated type : by declaring it, but not giving it a value.
Before this feature, if you wanted to make a trait that represented floating point numbers, you’d have to write this: This specific feature (associated type constructors) resolves one of the most common use cases for higher-kindedness, is a relatively simple extension to the type system compared to other forms of higher-kinded polymorphism, and is forward compatible with more complex forms of higher-kinded polymorphism that may be introduced in the future.
This trait cannot be expressed in Rust as it exists today, because it depends on a sort of higher-kinded polymorphism. This RFC would extend Rust to include that specific form of higher-kinded polymorphism, which is referred to here as associated type constructors.
This feature has a number of applications, but the primary application is along the same lines as the StreamingIterator trait: defining traits which yield types which have a lifetime tied to the local borrowing of the receiver type. “Higher-kinded types” is a vague term, conflating multiple language features under a single banner, which can be inaccurate.
As background, this RFC includes a brief overview of the notion of kinds and mindedness. Specifically, the goal of this RFC is to allow type constructors to be associated with traits, just as you can currently associate functions, types, and costs with traits.
This RFC proposes a very simple syntax for defining an associated type constructor, which looks a lot like the syntax for creating aliases for type constructors. The goal of using this syntax is to avoid to creating roadblocks for users who do not already understand higher mindedness.
Once a trait has an associated type constructor, it can be applied to any parameters or concrete term that are in scope. This RFC does not propose extending Arts to take type arguments, which makes these less expressive than they could be.
In contrast, where clauses on associated types introduce constraints which must be proven each time the associated type is used. (@nikomatsakis believes that where clauses will be needed on associated type constructors specifically to handle lifetime well fondness in some cases.
The exact details are left out of this RFC because they will emerge more fully during implementation.) This feature is not full-blown higher-kinded polymorphism, and does not allow for the forms of abstraction that are so popular in Haskell, but it does provide most of the unique-to- Rust use cases for higher-kinded polymorphism, such as streaming iterators and collection traits.
This RFC uses the terminology associated type constructor,” which has become the standard way to talk about this feature in the Rust community. This is not a very accessible framing of this concept; in particular the term type constructor” is an obscure piece of jargon from type theory which most users cannot be expected to be familiar with.
Upon accepting this RFC, we should begin (with haste) referring to this concept as simply “generic associated types.” Today, associated types cannot be generic; after this RFC, this will be possible.
Rather than teaching this as a separate feature, it will be taught as an advanced use case for associated types. This will also likely increase the frequency with which users have to employ higher rank trait bounds; we will want to put additional effort into teaching and making teachable Arts.
This would add a somewhat complex feature to the language, being able to polymorphically resolve type constructors, and requires several extensions to the type system which make the implementation more complicated. Additionally, though the syntax is designed to make this feature easy to learn, it also makes it more plausible that a user may accidentally use it when they mean something else, similar to the confusion between imply . For Trait and imply
This does not add all the features people want when they talk about higher- minded types. However, this feature is forward compatible with other kinds of higher-kinded polymorphism, and doesn't preclude implementing them in any way.
In fact, it paves the way by solving some implementation details that will impact other kinds of higher- mindedness as well, such as partial application. However, the syntax used for these other forms of higher-kinded polymorphism will depend on exactly what features they enable.
It would be hard to design a syntax which is consistent with unknown features. An alternative is to push harder on Arts, possibly introducing some elision that would make them easier to use.
However, this only partially prevents the effectiveness of StreamingIterator, only allows for some types that associated type constructors can express, and is in generally a hack attempt to work around the limitation rather than an equivalent alternative. Because of this, languages with first class higher minded polymorphism tend to impose restrictions on these higher minded terms, such as Haskell's currying rules.
These restrictions are quite constrictive; there are several applications of ATC's that we already know about that would be frustrated by this, such as the definition of Iterable for Yashmak (for which the item (&'a K, &'a V), applying the lifetime twice). This will mean that if higher order type constructors are ever added to the language, they will not be able to take an abstract ATC as an argument.