You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Question: in a trait or impl, should it be possible to call foo(x) where foo is a method in scope? We don't allow calling foo() (i.e., with an implicit self), but we do allow calling bar where bar is a function in scope. It seems to me like we should allow it since it is weird to need qualification of an item in an inner scope but not an outer one. OTOH, it means we would get worse error messages where a programmer has omitted an explicit self, since it would just be a "not enough args" error, rather than something more specific.
I've found a few consequences of the RFC that I haven't seen mentioned before, while trying to design a full implementation:
Resolve wouldn't need to track impls at all. This means UFCS reverts the need to force impls to be in the same module with their type.
structFoo<T>;mod bar {implsuper::Foo<()>{// impl allowed outside of Foo's module.fnbar(&self) -> Option<()>{None}}}implFoo<bool>{// same method name, allowed only because Self cannot overlap// with any other impl with the same method (as with trait impls).fnbar(&self) -> Option<bool>{Some(true)}}// anonymous impls would be checked by coherence as if they were// implementing traits from outside the current crate.impl<T>[Foo<T>]{fnextra(&self) -> usize{0}}impl<T>Vec<Foo<T>>{fnextra(&self) -> usize{self.capacity() - self.len()}}
All of these combined will end up removing some extension traits, and make type aliases much more powerful, without special-casing them. This will be possible, given we complete defaulted type params:
// std::collections (the collections crate wouldn't have a clue about RNGs):typeHashMap<K,V,H = RandomizedSipHash> = collections_crate::HashMap<K,V,H>;
On the other hand, i32::foo would only mean <i32>::foo, so you couldn't use modules like std::i32 at all. For this, I propose a relatively simple solution:
// in libcore:#[lang="i32"]enumi32{}// unihabited enum introduces a type in scope and nothing else// coherence would allow this impl as the type is "rooted" in the crate// defining the lang item.impli32{fnfoo(self) -> ... {...}}
That would also make the rustdoc special handling for primitives a bit simpler.
Then again, that last one isn't necessary. I just checked and the only items in std::i32 are a few constants which would be better off in the Int trait, once we get associated constants.
I would still like having all those primitives in the prelude, but I guess the situation isn't as dire.
EDIT: std::str has 3 free functions (and a bunch of types/traits), maybe it's a better example of a primitive type with an "associated" module.
structFoo(i32,i64);implFoo{fnclassic(&self){println!("{}, {}",self.0,self.1);}fndestructuring(&Foo(u, v):&Self){println!("{}, {}", u, v);}}fnmain(){let x = Foo(3,14);
x.classic();// Works, but doesn't destructureFoo::destructuring(&x);// Works, but x is not an invocant
x.destructuring();// Explodes}
<anon>:17:7: 17:22 error: no method named `destructuring` found for type `Foo` in the current scope
<anon>:17 x.destructuring(); // Explodes
^~~~~~~~~~~~~~~
<anon>:17:7: 17:22 note: found defined static methods, maybe a `self` is missing?
<anon>:8:5: 10:6 note: candidate #1 is defined in an impl for the type `Foo`
<anon>:8 fn destructuring(&Foo(u, v): &Self) {
<anon>:9 println!("{}, {}", u, v);
<anon>:10 }
error: aborting due to previous error
playpen: application terminated with error code 101
i.e., it supports destructuring Self in the most literal interpretation of the phrase, but it most certainly does not support destructuring the invocant.
Those are completely different. My Foo::destructuring() takes self by read-only reference, while yours takes it by value - and of course it can be destructured in the body; by then it's a variable like any other. That's not the point.
The point is that UFCS, as far as I can see, was specified as making it such that defining a first parameter of type [&[mut ]]Self as a trait function would permit method-call syntax. It's not destructuring in the argument list that's special either - for anything other than Self it works flawlessly, and even for Self it works when either invoked as a static function or in any parameter but the first.
The problem is that the method-call syntax is being forbidden when it should be allowed - i.e., exactly UFCS.
One use case where this is especially nice, for its pure concision: Implementing state machines where each state is a newtype.
(In fact, the distinction between static methods and other methods is completely erased, as per the method lookup of RFC PR #48.)
Thus, defining a function that takes a first argument of type [&[mut ]]Self inside an impl block should permit method call syntax, no? But even without destructuring, it fails: http://is.gd/tZKLR9
@eternaleye It refers to self-ish methods behaving like static methods when accessed via a path.
If you read the full RFC, you'll see that it only talks about paths.
Also, if you look at RFC PR 48, there's some interesting discussion about special-casin the first argument, but nothing other than self, &self, &mut self and ~self (now self: Box<Self>) is in the accepted RFC 48.
EDIT: I had missed "Receiver reconciliation" which was never fully implemented AFAICT.
Mm, I suppose I misunderstood then. I'll see about writing up an RFC myself.
EDIT: Er, on reading RFC 48 in the repo, what I'm seeing does not match what you say.
Under this proposal we would keep these shorthands but also permit any function in a trait to be used as a method, so long as the type of the first parameter is either Self or something derefable Self:
and
fn foo(self: &Self, ...) // equivalent to `fn foo(&self, ...)
and
It would not be required that the first parameter be named self, though it seems like it would be useful to permit it. It's also possible we can simply make self not be a keyword (that would be my personal preference, if we can achieve it).
@eternaleye it's true that we initially intended to not make the name significant, but I think that ship has sailed (and it's worth amending the RFC). For one thing, it would be backwards incompatible to make a change here, as it would introduce potential new ambiguities into method dispatch (though I don't know whether this would actually affect any crates in practice).
But also, I've shifted my opinion as to what I think is best. I think it is actively useful to be able to exclude functions from being used as methods, because it allows you to avoid potential conflicts between traits (any number of traits can have associated fns with the same name, but if method notation forces those names to be in conflict if a single type implements all the traits).
I also think there is just a certain amount of clarity in a rule like "associated fns whose first argument uses the self keyword can be used as methods"; it tells you what the trait author had in mind, and encourages a more uniform style on users of the trait.
It also means that we can impose a rule that says: "if you use the keyword self, the type must be derefable to Self", rather than us deducing whether the type is derefable to Self. Figuring out if something holds is generally much harder than requiring that it does hold, particularly around generic code.
Regarding conflict, ISTR an older syntax (from before <T as U>::foo()) that may have just been an idea, but was x.Trait::foo()
And personally, my interest in this is for cleanliness and concision in implementing things like state machines - in particular, being able to destructure the invocant lends itself very nicely to simple, concise implementations of state machines that can be used in a method-chaining notation.
I can certainly see where you're coming from regarding deciding whether the type is derefable to Self, though.
Given the above, perhaps I'll do up an RFC for permitting @-binding of the invocant, ugly as that would be 😞
Activity
[-]UFCS (Tracking RFC 46)[/-][+]Implement UFCS (Tracking RFC 46)[/+]pnkfelix commentedon Aug 7, 2014
This is (expected to be a) backwards-compatible change, so while we do desire it, it is not a strict necessity for 1.0
Assigning P-high, not 1.0 milestone.
nrc commentedon Oct 12, 2014
Question: in a trait or impl, should it be possible to call
foo(x)wherefoois a method in scope? We don't allow callingfoo()(i.e., with an implicit self), but we do allow callingbarwhere bar is a function in scope. It seems to me like we should allow it since it is weird to need qualification of an item in an inner scope but not an outer one. OTOH, it means we would get worse error messages where a programmer has omitted an explicit self, since it would just be a "not enough args" error, rather than something more specific.Allow passing self as an argument to methods
auto merge of #18053 : nick29581/rust/ufcs1, r=pcwalton
[-]Implement UFCS (Tracking RFC 46)[/-][+]Implement UFCS (Tracking RFC 132)[/+]alexchandel commentedon Dec 18, 2014
Issue #8888 is still referenced by the Float trait, which contains
unused_selfparameters.auto merge of #21077 : eddyb/rust/expr-qpath, r=nikomatsakis
eddyb commentedon Jan 23, 2015
I've found a few consequences of the RFC that I haven't seen mentioned before, while trying to design a full implementation:
Resolve wouldn't need to track
impls at all. This means UFCS reverts the need to forceimpls to be in the same module with their type.All of these combined will end up removing some extension traits, and make type aliases much more powerful, without special-casing them. This will be possible, given we complete defaulted type params:
On the other hand,
i32::foowould only mean<i32>::foo, so you couldn't use modules likestd::i32at all. For this, I propose a relatively simple solution:That would also make the rustdoc special handling for primitives a bit simpler.
Then again, that last one isn't necessary. I just checked and the only items in
std::i32are a few constants which would be better off in theInttrait, once we get associated constants.I would still like having all those primitives in the prelude, but I guess the situation isn't as dire.
EDIT:
std::strhas 3 free functions (and a bunch of types/traits), maybe it's a better example of a primitive type with an "associated" module.21 remaining items
nrc commentedon Apr 23, 2015
Hmm, what still needs to be done here? Anything other than self argument destructuring (see the above two comments).
ihrwein commentedon May 30, 2015
I stumbled into destructoringselftoday. It would be nice to see it working.I hit an other problem, it works fine.
eternaleye commentedon Jun 17, 2015
@ihrwein, It still does not work in my testing:
i.e., it supports destructuring
Selfin the most literal interpretation of the phrase, but it most certainly does not support destructuring the invocant.ihrwein commentedon Jul 13, 2015
@eternaleye try this way:
eternaleye commentedon Jul 13, 2015
@ihrwein
Those are completely different. My
Foo::destructuring()takes self by read-only reference, while yours takes it by value - and of course it can be destructured in the body; by then it's a variable like any other. That's not the point.The point is that UFCS, as far as I can see, was specified as making it such that defining a first parameter of type
[&[mut ]]Selfas a trait function would permit method-call syntax. It's not destructuring in the argument list that's special either - for anything other thanSelfit works flawlessly, and even forSelfit works when either invoked as a static function or in any parameter but the first.The problem is that the method-call syntax is being forbidden when it should be allowed - i.e., exactly UFCS.
One use case where this is especially nice, for its pure concision: Implementing state machines where each state is a newtype.
alexcrichton commentedon Feb 18, 2016
I believe this has all long since been implemented, so closing.
eternaleye commentedon Feb 18, 2016
@alexcrichton: No, my example is still broken (destructuring the invocant): http://is.gd/3i9oCg
eddyb commentedon Feb 18, 2016
@eternaleye The accepted RFC supports nothing of the sort.
eternaleye commentedon Feb 18, 2016
@eddyb: From the opening paragraph:
Thus, defining a function that takes a first argument of type
[&[mut ]]Selfinside animplblock should permit method call syntax, no? But even without destructuring, it fails: http://is.gd/tZKLR9eddyb commentedon Feb 18, 2016
@eternaleye It refers to
self-ish methods behaving like static methods when accessed via a path.If you read the full RFC, you'll see that it only talks about paths.
Also, if you look at RFC PR 48, there's some interesting discussion about special-casin the first argument, but nothing other thanself,&self,&mut selfand~self(nowself: Box<Self>) is in the accepted RFC 48.EDIT: I had missed "Receiver reconciliation" which was never fully implemented AFAICT.
eternaleye commentedon Feb 18, 2016
Mm, I suppose I misunderstood then. I'll see about writing up an RFC myself.
EDIT: Er, on reading RFC 48 in the repo, what I'm seeing does not match what you say.
and
and
(emphasis mine)
nikomatsakis commentedon Feb 18, 2016
@eternaleye it's true that we initially intended to not make the name significant, but I think that ship has sailed (and it's worth amending the RFC). For one thing, it would be backwards incompatible to make a change here, as it would introduce potential new ambiguities into method dispatch (though I don't know whether this would actually affect any crates in practice).
But also, I've shifted my opinion as to what I think is best. I think it is actively useful to be able to exclude functions from being used as methods, because it allows you to avoid potential conflicts between traits (any number of traits can have associated fns with the same name, but if method notation forces those names to be in conflict if a single type implements all the traits).
I also think there is just a certain amount of clarity in a rule like "associated fns whose first argument uses the
selfkeyword can be used as methods"; it tells you what the trait author had in mind, and encourages a more uniform style on users of the trait.It also means that we can impose a rule that says: "if you use the keyword
self, the type must be derefable toSelf", rather than us deducing whether the type is derefable toSelf. Figuring out if something holds is generally much harder than requiring that it does hold, particularly around generic code.eternaleye commentedon Feb 18, 2016
Regarding conflict, ISTR an older syntax (from before
<T as U>::foo()) that may have just been an idea, but wasx.Trait::foo()And personally, my interest in this is for cleanliness and concision in implementing things like state machines - in particular, being able to destructure the invocant lends itself very nicely to simple, concise implementations of state machines that can be used in a method-chaining notation.
I can certainly see where you're coming from regarding deciding whether the type is derefable to Self, though.
Given the above, perhaps I'll do up an RFC for permitting
@-binding of the invocant, ugly as that would be 😞