rust-lang / rust-lang/rfcs

Tentative: impl chaining

Open
#1,958 11 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

T-lang
Dominant language
Markdown
Stars
6.6k
Forks
1.7k
Avg merge
16h 14m
Merged PRs (30d)
1

Description

Summary

'impl chaining' allows declaring impls on items without repeating the type variable declarations.

Motivation

Generics with bounds can become rather verbose. Consider this example:


use std::ops::Index;
use std::marker::PhantomData;

pub struct TypedArray<IdxIn, IdxOut, T>
where
    T: Index<IdxOut>,
    IdxIn: Into<IdxOut>,
{
    val: T,
    indexers: PhantomData<(IdxIn, IdxOut)>,
}

// 'free impl'
impl<IdxIn, IdxOut, T> Index<IdxIn> for TypedArray<IdxIn, IdxOut, T>
where
    T: Index<IdxOut>,
    IdxIn: Into<IdxOut>,
{
    type Output = T::Output;
    
    fn index(&self, index: IdxIn) -> &Self::Output {
        &self.val[index.into()]
    }
}

This proposes some syntax to sop up the un-DRY repetition:


use std::ops::Index;
use std::marker::PhantomData;

pub struct TypedArray<IdxIn, IdxOut, T>
where
    T: Index<IdxOut>,
    IdxIn: Into<IdxOut>,
{
    val: T,
    indexers: PhantomData<(IdxIn, IdxOut)>,
}

// 'chained impl'
in impl Index<IdxIn> {
    type Output = T::Output;
    
    fn index(&self, index: IdxIn) -> &Self::Output {
        &self.val[index.into()]
    }
}

Detailed design

The feature is called "impl chaining", and the in impl item is a "linked impl". The original impls are hereby dubbed "free impl".

Semantics

As the compiler parses the module or block expression, the type variables of the most recently defined enum, struct, or type alias is kept in memory.
(eg, Option<Scope>)
Certain items reset this state, and some items do not affect it.
Functions and modules reset it.
Imports do not reset it ???
Variable, constants, and statics reset also?

A linked impl can of course follow another linked impl.
They can also introduce new type variables, and add additional bounds,
but these changes are scoped only to that specific link, and is not visible to any following links.
If the user wants to add a bound used by multiple impl links,
they can set up the desired scope by adding a (possibly non-empty!) free impl.

Syntax

I think with impl would look the most natural, however this (probably) has ambiguities when parsing block expressions, and impl non_keyword would break compatibility. in and for are the only keywords that could possibly make any sense for impl chaining, so that gives these options:

  • in impl
  • impl in
  • for impl
  • impl for

How We Teach This

What names and terminology work best for these concepts and why? 
How is this idea best presented—as a continuation of existing Rust patterns, or as a wholly new one?

Would the acceptance of this proposal change how Rust is taught to new users at any level? 
How should this feature be introduced and taught to existing Rust users?

What additions or changes to the Rust Reference, _The Rust Programming Language_, and/or _Rust by Example_ does it entail?

Drawbacks

Old code would like to be changed to use this.

It makes the language more complicated.

This might encourage sloppier code. It could encourage proliferation of unnecessary bounds, because a struct can have laxer bounds than an impl.

It might encourage re-arranging the file in less-than-ideal ways.

The example doesn't seem very convincing to me. Maybe there there aren't any examples that are really all that convincing.

Maybe the pain of it will encourage the production of Very Fancy Rust IDEs.

I was surprised to learn that you can do a free impl on a type alias.
Maybe other people would be surprised by this and inadvertently chain an impl to a type alias.

Alternatives

What other designs have been considered? What is the impact of not doing this?
  • Leave it be.
  • Allow functions to be declared in a struct/enum body:
struct Thing {
    field: i32,
    
    fn new() -> Self {
        Thing { field: 5 }
    }
}

This is not as powerful, but it is simple & what other languages do.

Unresolved questions

What parts of the design are still TBD?

Better names?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the issue's Detailed design, especially the Semantics and Syntax sections, then review Alternatives and Unresolved questions. Done would require settled syntax and semantics for impl chaining; this payload identifies no implementation files or tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.