Alias for associated type of supertrait - rust

I have the following trait:
use std::ops::Index;
trait Map: Index<<Self as Map>::Key> {
type Key;
}
Index has the associated type Output. I semantically want this type, but I don't like the name Output in my API. Thus I would like to add an alias for that type.
I tried this (the same syntax for normal type aliases):
trait Map: Index<<Self as Map>::Key> {
type Key;
type Value = <Self as Index<Self::Key>>::Output;
}
However, this results in an error:
error[E0658]: associated type defaults are unstable (see issue #29661)
--> src/main.rs:9:9
|
9 | type Value = <Self as Index>::Output;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
From the tracking issue I could gather that this syntax is apparently used for associated types that can be overwritten by implementors. But I don't want implementors to overwrite this type, I always want Map::Value == Map::Output.
Is this possible somehow?

The associated item RFC states:
Type parameters to traits can either be "inputs" or "outputs":
Inputs. An "input" type parameter is used to determine which impl to use.
Outputs. An "output" type parameter is uniquely determined by the impl, but plays no role in selecting the impl.
The RFC also clarifies trait matching by:
Treating all trait type parameters as input types, and
Providing associated types, which are output types.
From these descriptions it is clear that an associated type is by design in control by the impl, so it is not possible to block implementors from overwriting the type.
A workaround for obtaining some form of control over the implementor may be to define a default method that uses the associated type, for example:
pub trait Map: Index<<Self as Map>::Key> {
type Key;
type Value = <Self as Index<<Self as Map>::Key>>::Output;
#[doc(hidden)]
fn invalid_operation() -> Option<&'static <Self as Index<<Self as Map>::Key>>::Output> {
None
}
}
Now for the implementors is not more possible to simply override the default Value type because the default method invalid_operation no longer typecheck.
Note also the doc(hidden) feature that strips the default method from the docs.
The hidden method name may be chosen to convey some information.
For the above example, the implementor get the error message:
error[E0399]: the following trait items need to be reimplemented as `Value` was overridden: `invalid_operation`
As you already know, assigning default associated types is not permitted in current stable Rust, a nightly version must be used and the feature has to be enabled with:
#![feature(associated_type_defaults)]

Related

Associated type with bound is not being enforced when used as function parameter

In the following example code the trait Foo requires that the associated type X implement the Clone trait.
When using the impl Foo<X = Baz> syntax in the do_it function signature, cargo check does not complain that Baz does not implement the Clone trait.
However, cargo check does complain about this issue, in the impl Foo for Bar block.
I would have expected impl Foo<X = Baz> to complain in the same way.
trait Foo {
type X: Clone;
}
struct Bar;
struct Baz;
impl Foo for Bar {
type X = Baz; // <- complains Baz does not impl Clone trait
}
fn do_it(foo: impl Foo<X = Baz>) {} // <- does not complain
This is not the case if X is a generic parameter. In that case, cargo check indicates that the Clone trait bound is not satisfied by foo: impl Foo<Bar>
trait Foo<X>
where
X: Clone,
{
}
struct Bar;
struct Baz;
fn do_it(foo: impl Foo<Baz>) {} // <- complains Baz does not impl Clone trait
Is this intended behavior and if so why?
This is described in the RFC introducing associated types, in a short sentence:
The BOUNDS and WHERE_CLAUSE on associated types are obligations for the implementor of the trait, and assumptions for users of the trait:
trait Graph {
type N: Show + Hash;
type E: Show + Hash;
...
}
impl Graph for MyGraph {
// Both MyNode and MyEdge must implement Show and Hash
type N = MyNode;
type E = MyEdge;
...
}
fn print_nodes<G: Graph>(g: &G) {
// here, can assume G::N implements Show
...
}
What this means is that the person that is responsible to prove that the bounds hold is not the user of the trait (do_it() in your example) but the implementor of the trait. This is in contrast to generic parameters of traits, where the proof obligation is on the user.
The difference should be obvious when you look at it: with generic parameters, the types are foreign and unknown inside the trait implementation, so it must assume the bounds hole. The user of the trait, on the other hand, has concrete types for them (even if they're themselves generic, they're still concrete types from the point of view of the trait) and so it should prove the bounds hold. In contrast, with associated types the story is different: the implementor knows the concrete type, while the user assumes a generic type (even if, like in your code, it constrains them to a specific type, in the general case it is still unknown).
Note that with where bounds on associated types (type Foo where Self::Foo: Clone), that were introduced with generic associated types (yes, I know the RFC I linked bring them, but as far as I know they were not implemented and eventually implemented as part of GATs with different semantics), the story is again different from normal associated type bounds: the user has to prove them too (I think the both need to prove, but I'm not sure). This is because they're expected to be used for generic parameters on associated types, so they're similar to generic parameters on traits, or where clauses in them.

Pass struct generic type to trait generic method [duplicate]

In this question, an issue arose that could be solved by changing an attempt at using a generic type parameter into an associated type. That prompted the question "Why is an associated type more appropriate here?", which made me want to know more.
The RFC that introduced associated types says:
This RFC clarifies trait matching by:
Treating all trait type parameters as input types, and
Providing associated types, which are output types.
The RFC uses a graph structure as a motivating example, and this is also used in the documentation, but I'll admit to not fully appreciating the benefits of the associated type version over the type-parameterized version. The primary thing is that the distance method doesn't need to care about the Edge type. This is nice but seems a bit shallow of a reason for having associated types at all.
I've found associated types to be pretty intuitive to use in practice, but I find myself struggling when deciding where and when I should use them in my own API.
When writing code, when should I choose an associated type over a generic type parameter, and when should I do the opposite?
This is now touched on in the second edition of The Rust Programming Language. However, let's dive in a bit in addition.
Let us start with a simpler example.
When is it appropriate to use a trait method?
There are multiple ways to provide late binding:
trait MyTrait {
fn hello_word(&self) -> String;
}
Or:
struct MyTrait<T> {
t: T,
hello_world: fn(&T) -> String,
}
impl<T> MyTrait<T> {
fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>;
fn hello_world(&self) -> String {
(self.hello_world)(self.t)
}
}
Disregarding any implementation/performance strategy, both excerpts above allow the user to specify in a dynamic manner how hello_world should behave.
The one difference (semantically) is that the trait implementation guarantees that for a given type T implementing the trait, hello_world will always have the same behavior whereas the struct implementation allows having a different behavior on a per instance basis.
Whether using a method is appropriate or not depends on the usecase!
When is it appropriate to use an associated type?
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. It is not the only way (thus the question):
trait MyTrait {
type Return;
fn hello_world(&self) -> Self::Return;
}
Or:
trait MyTrait<Return> {
fn hello_world(&Self) -> Return;
}
Are equivalent to the late binding of methods above:
the first one enforces that for a given Self there is a single Return associated
the second one, instead, allows implementing MyTrait for Self for multiple Return
Which form is more appropriate depends on whether it makes sense to enforce unicity or not. For example:
Deref uses an associated type because without unicity the compiler would go mad during inference
Add uses an associated type because its author thought that given the two arguments there would be a logical return type
As you can see, while Deref is an obvious usecase (technical constraint), the case of Add is less clear cut: maybe it would make sense for i32 + i32 to yield either i32 or Complex<i32> depending on the context? Nonetheless, the author exercised their judgment and decided that overloading the return type for additions was unnecessary.
My personal stance is that there is no right answer. Still, beyond the unicity 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.
Associated types are a grouping mechanism, so they should be used when it makes sense to group types together.
The Graph trait introduced in the documentation is an example of this. You want a Graph to be generic, but once you have a specific kind of Graph, you don't want the Node or Edge types to vary anymore. A particular Graph isn't going to want to vary those types within a single implementation, and in fact, wants them to always be the same. They're grouped together, or one might even say associated.
Associated types can be used to tell the compiler "these two types between these two implementations are the same". Here's a double dispatch example that compiles, and is almost similar to how the standard library relates iterator to sum types:
trait MySum {
type Item;
fn sum<I>(iter: I)
where
I: MyIter<Item = Self::Item>;
}
trait MyIter {
type Item;
fn next(&self) {}
fn sum<S>(self)
where
S: MySum<Item = Self::Item>;
}
struct MyU32;
impl MySum for MyU32 {
type Item = MyU32;
fn sum<I>(iter: I)
where
I: MyIter<Item = Self::Item>,
{
iter.next()
}
}
struct MyVec;
impl MyIter for MyVec {
type Item = MyU32;
fn sum<S>(self)
where
S: MySum<Item = Self::Item>,
{
S::sum::<Self>(self)
}
}
fn main() {}
Also, https://blog.thomasheartman.com/posts/on-generics-and-associated-types has some good information on this as well:
In short, use generics when you want to type A to be able to implement a trait any number of times for different type parameters, such as in the case of the From trait.
Use associated types if it makes sense for a type to only implement the trait once, such as with Iterator and Deref.

Function signature for generic numbers

I am trying to make a few generic functions that work on numbers, but I'm struggling with the function signatures.
Perhaps I am attacking the problem from the wrong angle, but here is where I got on my own so far. I am not hellbent on making this work this way; so if I am attacking the problem (of creating a small lib of generally useful math functions) from the wrong angle, then by all means educate me.
Let's say I want a function, add that adds up two numbers:
use std::ops::Add;
fn add(a: Add, b: Add) -> Add::Output {
a + b
}
This won't compile. Here is a playground though: https://play.integer32.com/?version=stable&mode=debug&edition=2015&gist=4589325b5c8d1f1b19440424878caa98
I get essentially two errors. The first:
error[E0393]: the type parameter `RHS` must be explicitly specified
--> src/main.rs:8:11
|
8 | fn add(a: Add, b: Add) -> Add::Output {
| ^^^ missing reference to `RHS`
|
= note: because of the default `Self` reference, type parameters must be specified on object types
I have read the chapter on advanced traits in the Rust book, so i "sort-of/kind-of" understand the RHS message, but they attack the problem of adding the Add trait to your particular data structure (a Point in the example); but never show a function signature of a function that takes anything that can be added up. So I am a little lost.
The second error:
error[E0223]: ambiguous associated type
--> src/main.rs:8:27
|
8 | fn add(a: Add, b: Add) -> Add::Output {
| ^^^^^^^^^^^ ambiguous associated type
|
= note: specify the type using the syntax `<Type as std::ops::Add>::Output`
This goes away if I write <i32 as Add>::Output, but that is not what I want. I specifically want the function to work on anything that can be added up (assuming both a and b to be the same type).
You are conflating traits and types.
Add is a trait. A trait can be implemented for a type or a class of types, but it is not a type itself.
Function arguments need to be declared with a type, not a trait. This is the main problem with your prototype – Add is not a type, so you can't use it as the type of a variable or a function argument.
Rust allows you to declare generic types, essentially type variables. You can then place trait bounds on the generic types, which require that whatever type is substituted for the generic type must implement some trait. Your example using a generic type parameter T looks like this:
fn add<T: Add>(a: T, b: T) -> T::Output
This prototype requires that a and b both have the same type T, and that T implements the Add trait.

Can't implement a trait I don't own for all types that implement a trait I do own

pub trait AllValues {
fn all_values() -> Vec<Self> where Self: std::marker::Sized;
}
use rand::Rand;
use rand::Rng;
impl<T: AllValues + Sized> Rand for T {
fn rand<R: Rng, T>(rng: &mut R) -> T {
let values = T::all_values();
let len = values.len();
if len == 0 {
panic!("Cannot pick a random value because T::all_values() returned an empty vector!")
} else {
let i = rng.gen_range(0, len);
values[i]
}
}
}
The preceding code produces the following compile-time error:
error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`); only traits defined in the current crate can be implemented for a type parameter
--> src/lib.rs:137:1
|
137 | impl<T: AllValues + Sized> Rand for T {
| ^
According to the restrictions on implementing traits mentioned here I should be able to implement Rand for AllValues since AllValues is defined in my crate. Is this actually allowed by the coherence/orphan impls rules? And if so, what is the right way to implement Rand for things that implement AllValues?
I should be able to implement Rand for AllValues since AllValues is defined in my crate.
No, you are only allowed to implement your own trait AllValues for types you didn't define. You can't make the logical jump to implementing an unrelated trait that you also didn't define.
There are two considerations to remember:
If your trait is public (which it is based on the code you've provided), you aren't the only one that can implement the trait. Consumers of your crate might be able to implement it for their own types, where they might also decide to implement Rand!
The rand crate might decide to implement Rand for T some time in the future.
What is the right way to implement Rand for things that implement AllValues?
I don't believe there is one. I'd just introduce a wrapper type that holds a value or a reference to a value that implements your trait and implement Rand for that.
See also:
How do I implement a trait I don't own for a type I don't own?
I see now where my mistake in interpretation was. Quoting from the the traits section of the book:
There’s one more restriction on implementing traits: either the trait
or the type you’re implementing it for must be defined by you. Or more
precisely, one of them must be defined in the same crate as the impl
you're writing.
(Emphasis added.)
Since I was trying to implement a trait I must have read that as "either the trait or the trait you’re implementing it for". This discussion about an eventually implemented rfc specifically mentions a similar case to the one I presented : impl<T: Copy> Clone for T as something that would not be allowed.
Creating a wrapper type as suggested elsewhere is one solution for this problem. Assuming the ownership of the type implementations allows it, implementing the trait explicitly for each concrete instance, (optionally condensing the code with a macro,) as suggested here is another.

Requiring a trait bound on the associated type of an inherited trait

I have a trait Foo inheriting from another trait Bar. Bar has an associated type Baz. Foo constrains Baz such that Baz must implement Hoge.
trait Hoge {}
trait Bar {
type Baz;
}
trait Foo: Bar where Self::Baz: Hoge {}
However, when I define a generic function requiring the generic type T to implement Foo,
// [DESIRED CODE]
fn fizz<T: Foo>(buzz: T) {
// ...
}
rustc complains with EO277 unless I constrain T explicitly:
fn fizz<T: Foo>(buzz: T) where T::Baz: Hoge {
// ...
}
I do not understand why I need to do this. I would like to be able to write [DESIRED CODE]. What is the recommended way to do this?
Sadly (or not), you have to repeat the bounds.
Last year I opened a issue thinking that the type checker was being inconsistent. The code is similar to yours.
#arielb1 closed the issue and said that this was the intended behavior and gave this explanation:
The thing is that we don't want too many bounds to be implicitly
available for functions, as this can lead to fragility with distant
changes causing functions to stop compiling. There are basically 3
kinds of bounds available to a function:
bounds from explicit where-clauses - e.g. T: B when you have that clause. This includes the "semi-explicit" Sized bound.
bounds from supertraits of explicit where-clauses - a where-clause adds bounds for its supertraits (as trait B: A, the T: B bound adds a
T: A bound).
bounds from the lifetime properties of arguments (outlives/implicator/implied bounds). These are only lifetime bounds,
and irrelevant for the current problem. rust-lang/rfcs#1214 involved
them a great deal.
If your bound isn't in the list, you will have to add it explicitly if
you want to use it. I guess this should be a FAQ entry.
Today I opened an issue to request that this information to be added to the docs.
It is possible to work around this behaviour by using another associated type in Foo since the compiler accepts implicit bounds when part of the associated type definition (playground):
trait Foo: Bar<Baz = Self::HogeBaz> {
type HogeBaz: Hoge;
}
The new associated type can be hidden in a helper trait to avoid having to include it in every implementation. Full example (with renaming for clarity)
(playground)
trait Bound {
fn bound();
}
trait Trait {
type Type;
}
trait BoundedTypeHelper: Trait<Type = Self::BoundedType> {
type BoundedType: Bound;
}
impl<T> BoundedTypeHelper for T
where
T: Trait,
Self::Type: Bound,
{
type BoundedType = Self::Type;
}
trait UserTrait: BoundedTypeHelper {}
fn fizz<T: UserTrait>() {
T::Type::bound()
}
I am with you in thinking that the original where-based bound ought to be treated as part of the trait definition and applied implicitly. It feels very arbitrary that bounding associated types inline works but where clauses do not.

Resources