-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementing multiple interfaces with a single impl
definition
#4566
Comments
Agreement on the constrained impls approach. It seems really important to have access to the associated types with certainty, especially in generic code using the impl as you mention. |
I came here to write up something in favor of the "constrained impls" approach. But I've been exploring the consequences and I am worried. What per-interface implementations would we have for let F:! type = J where .T = i32;
impl forall [U:! F - I] U as I where .T = i32 {}
impl forall [U:! F - K] U as K {
fn G() {}
}
impl forall [U:! F - L] U as L {
fn H() -> i32 { return U.F(); }
}
impl forall [U:! F - J] U as J {
fn F() -> i32 { return 0; }
} ... where Problem is, this appears to be circular. How would we determine which of those One way out of this would be to arrange our rules so that we have a total ordering for the implementations that we're going to pick -- for example, we select the interface X { let XT:! type; fn XF() -> XT; }
interface Y { let YT:! type; fn YF() -> YT; }
impl forall [T:! type] T as X where .XT = () { fn XF() {} }
impl forall [T:! type] T as Y where .YT = () { fn YF() {} }
impl forall [T:! type, U:! type] (T, U) as X & Y where .XT = i32 and .YT = i32 {
fn XF() -> i32 { return YF(); }
fn YF() -> i32 { return XF(); }
} Here, we can't select which impl we will use to implement impl forall [T:! type] (T, T) as X where .XT = i64 {
fn XF() -> i64 { return 0; }
} I'm not sure there's a well-defined best answer if we use this rule: note that we could either have I think there's another case which may seem distinct but is at least somewhat coupled to this decision... what happens in the case where a parameterized impl decomposes into a non-parameterized impl for some interface? For example: interface A {
fn F();
}
interface B(T:! type) {
extend A;
fn G();
}
impl forall [T:! type] i32 as B(T) {
fn F() {}
fn G() {}
} I think our options here are:
I don't have a good intuition for what I want to happen here. It seems like the definition of |
Only addressing the last question ("what happens in the case where a parameterized impl decomposes into a non-parameterized impl for some interface?"): I do think we want to allow the definition of So I'd like one of the last two options. I think the last option is what developers would want to write, and better supports evolution of the interface |
@zygoloid and I discussed this on Tuesday 2024-12-03. We came up with some additional concerns with implementing multiple interfaces, particularly as the result of one interface extending another.
We considered some restrictions on
None of these restrictions were sufficient to address the concerns. We ultimately decided that we likely wanted simpler rules:
Note that we still wanted to allow facet type expressions to the right of Also note that we were more open to relaxing the second rule (so you could implement multiple interfaces in a single impl declaration) in the future than the first rule. This would require first defining the (potentially complicated) logic to for disentangling the implementations of the interfaces from a single impl definition, and would follow the "Independent impls" approach. In this proposal, it would be less convenient to implement an interface together with the interfaces it extends, and evolutionary changes to split an interface into two with an The "subsumption" use case of "when an interface X is implemented, Y will be implemented without the user having to write a separate impl definition" will instead be handled using blanket implementations. There are a couple of variations:
Note that the orphan rule prevents this blanket impl from being written unless the two interfaces in a subsumption relationship are in the same library. Future work:
|
Summary of issue:
An
impl
specifies how a type satisfies a facet type, which may include multiple interfaces due to usingextend
or&
. Question: what happens when such animpl
is parameterized, and the implementation of a subset of the interfaces is superseded by a more specializedimpl
?There are three broad approaches that have been considered in discussion:
Details:
Consider these interface definitions:
Imagine we have a blanket implementation of
J
, and a specialization ofI
:The questions are then: Is this code valid? If not, what part is diagnosed as invalid? If it is, are the interfaces
J
,K
, andL
implemented forbool
? If so, what signatures doJ.F
andL.H
have? Is the situation any different forf64
?We don't want to be in a situation where generic code thinks the blanket impl applies but has the wrong idea about the return type of
F()
orH()
since itJ
,K
, andL
are not implemented forbool
and also not implemented forf64
, due to the specializations 4 & 5 blocking the blanket impl from applying.J.F
andL.H
in the blanket impl can't assume thatI.T
isi32
since that is an associated constant from another interface, so the functions 1 & 3 fail type checking. There isn't a problem, though, with the implementation (2) ofK
orK.G
.J
,K
, andL
are not implemented forbool
, since the blanket implementation has a constraint that says it only applies when.T
isi32
(like it says right there in the declaration). They are implemented forf64
, since the specialized implementation (5) ofI
forf64
has.T
equal toi32
. When type checking the blanket implementation, declarations (1) and (3) can assumeI.T
andL.X
arei32
, and thatI
,J
,K
, andL
are all implemented, but nothing about the body of any of the associated functions from other interfaces (which are generally treated as opaque anyway, unless those functions are executed at compile time).The text was updated successfully, but these errors were encountered: