RFC or tracking issue for Self<T>?

I just realized, that Self can not have generics (Self<T>), is this a feature or just something, that will be implemented in the future?

struct Example<T> {}

impl<T> Example<T> {
    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Self<U>;
}

AFAIK this is intentional: Self is not Example (the generic type), but Example<T> (the concrete type).

6 Likes

Think of it like this: you're implementing the method on the type Example<T>, not on Example. Note that you can totally have an impl Example<i32> to add methods on only the Example<i32> type, and in that case the type you're implementing on is Example<i32>, so that's what Self is.

If you need to return some other type such as Example<U>, then type that.

3 Likes

I'd say there's several valid ways to look at this issue. As you mentioned, on a type level, Example<i32> and Example<i64> are completely different, because generics are just a template and one can imagine, that the compiler creates a type Example__i32 and Example__i64 for those two arguments provided to the Example<T> template. One could argue, that Self ought to reflect only full types, which comes with a lot of conveniences for generic types, as you don't have to repeatedly type the generic parameters. It follows the DRY principle and I can't complain about it.

However, on a more abstract level, which the templates themselves are on, partial types do exist and we deal with them on a regular basis. They exist for the same DRY principles Self exists. In that regard, one could argue, that there ought to be a way to refer to the partial type Example in a DRY manner, thus the legitimate question about Self referring to the partial type.

However, it is in conflict with the first definition of Self, so we have to pick one. Do we really, though? I don't see a reason to reject the request, just because someone had made a decision in the past about how Self should work and assume, that everything that has been done is perfect and shall never be touched, again. That conservative approach just leads to stagnation and by that standard, Rust wouldn't even exist in the first place. There is always something, that can be improved, optimized, simplified and sometimes novel solutions appear for problems, that thought be "perfectly" optimized.

Personally, I could imagine that Self is just a shortcut for Self<T> and Self<U> could work just fine. If we're already dealing with templates anyway, why would I have be so concrete about Example__i32, when all I'm seeing in the code is Example<T>? Self could be handled just like any other template.

In the end, all that matters is how much work someone is willing to put into their ideas. Finding people with shared interests is always a good start, but it all boils down to someone having to implement the requested feature. If no one is willing to do that, even the best ideas are worth nothing. Therefore, you either do it yourself or you better be good at marketing your idea, to find someone else who does it for you.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.