The book states:
Methods take a special first parameter, of which there are three variants:
self,&self, and&mut self.
The reference uses typed self in a few places, but doesn't explain the exact rules.
The compiler also prints redundant and misleading diagnostics:
struct S;
impl S {
fn f(self: Vec<Self>) {}
}
gives:
<anon>:6:10: 6:28 error: mismatched self type: expected `S`: expected struct `S`, found struct `collections::vec::Vec` [E0211]
<anon>:6 fn f(self: Vec<Self>) {}
^~~~~~~~~~~~~~~~~~
Since replacing Vec<Self> with Box<Self> makes the code compile, "expected struct S" isn't quite correct.
Explicitly specifying the type of self using the syntax self: Box<Self> is sort of vestigial. The feature of allowing Box<Self> as the type of self, along with the syntax for explicitly writing out the type of self, sort of slipped into 1.0 without anyone really considering the issue. The other syntax is strongly preferred.
The explicit type syntax will probably be deprecated at some point.
/cc @rust-lang/lang , what do you think? Should we explain this more, or leave it as is?
@eefriedman On the contrary, self: Foo<Self> is part of UFCS, and should be implemented at some point.
I discussed the necessary changes to the way we store inherent impls with @arielb1 some time ago, though I can't recall who was asking in the first place.
One constraint we could put on it is that Foo<Self>: Deref<Target=Self>, which is easy to check and would allow self: Rc<Self> - although, if we want to use this for traits, as well, it would have to be smarter and only allow pointers.
Foo<Self> can probably be tested for object-safe compatibility inside a trait definition by replacing Self with the trait and checking Foo<Trait>: CoerceUnsized<Foo<Self>>.
Only one leaf field can be coerced (and it's a pointer), the others are forced to have the same representation (same type, or PhantomData<T> -> PhantomData<U>).
That implies you could have, e.g. self: Triplet<String, Rc<Self>, bool> in a trait and be object-safe, with virtual calls passing that entire ADT to the virtual method, modulo the vtable pointer (though keeping it doesn't hurt anyone, either, especially if the argument is passed with indirection).
@eddyb
compatibility with the trait? I would prefer to store explicit Self explicitly and use the normal check_method code.
If we want decent performance, requiring the method to appear in the autoderef loop would be nice (so do the autoderef loop in a probe, gather all methods, then sort them by priority and find the first matching one).
@arielb1 Wording was unclear, I edited that bit.
I was talking about a custom self type, like self: Rc<Self> in a method inside a trait definition, that can still be treated as object-safe, given that we can prove it is pointer-like (and can thus be passed to a virtual method).
CoerceUnsize is currently only implemented on pointers and pointers with data around them, so it fits the bill.
untagging docs and tagging with lang until @rust-lang/lang tells me what they want here
Triage: over a year later, nothing has happened with more exotic Selfs. While it's strictly true that Box<Self> will also work, making the diagnostic technically incorrect, this seems very minor. In addition, the book isn't going to cover this until it's made more general.
So: closing this ticket, in my mind, involves fixing the diagnostic to say "or Box".
(FWIW, I think we really want, at minimum, *mut Self and so forth to work. Not having support for this is quite frustrating in Rayon, at least if I try to be careful about my types in unsafe code.)
Nominating for lang team discussion. I assume this needs an RFC? Or perhaps there already was one, just not yet implemented.
I think an RFC would be a good idea, just so we spend some time thinking carefully about the cases. In particular, it's important to work out how this interacts with trait objects.
Some notes from the @rust-lang/lang meeting:
Self".self: Rc<Self>.If someone is interested in pursuing it, we'd be happy to work with them (feel free to ping me), but it's not the highest priority at the moment.
I think this issue has just gained some fire for prioritization with the recent introduction of generators and async/await.
See https://github.com/alexcrichton/futures-await/tree/e09c87c7aa796bf6e5b6d8d45d6cba2078d12210#borrowing for a motivating example for self: Rc<Self>.
@nikomatsakis @arielb1 Can we consider this for the impl period?
@aturon
I suspect this needs an actual RFC. However, with the newly-fixed method dispatch, I don't expect any obstacles to implementing this.
I mean, for implementing it in method dispatch. The interesting part that actually needs the RFC is the compiler being able to downcast the Rc<Trait> to an Rc<SomeSpecificType>.
With the current ABI, this is basically done in the compiler-generated impl of <Trait as Trait>::method, which means it needs to be "uniform" and not be specific to the specific type.
Maybe have the compiler do a reverse unsized coercion? That's it, we can have this object safety rule:
foo: for<LTS> fn(ε, ...χ) method
χ does not refer to Self
(used to be `ε` is of the form &Self, &mut Self, Box<Self>)
Γ, Self: Sized ⊢ for<LTS> ε: CoerceUnsized<ε[Self ← dyn Self<Assoc1=Self::Assoc1,...>]>
\-- this is in the trait param env, where we know that ε: Trait
------------
Γ ⊢ foo object-safe
Actually, I'm not sure that would work, because you might have the unsized coercion go through a different Unsize impl, say using an associated type (e.g. the example from #44861). We ICE on that (in trans), so maybe there's good room to lock things up somewhat. Or maybe just add an additional trait:
pub trait CoerceSized<Target> {
type SizeSource: ?Sized;
type SizeTarget: Unsize<Self::SizeSource>;
}
and have the bound be
Γ, Self: Sized ⊢ for<LTS> {
ε: CoerceSized<
ε[Self ← dyn Trait<Assoc1=Self::Assoc1,...>],
SizeSource=dyn Trait<Assoc1=Self::Assoc1,...>,
SizeTarget=Self
>
}
I believe we can then implement <Trait as Trait>::method "as follows":
(sized_abstract, vtable) = coerce_sized self
fn_ptr = vtable[INDEX]
ret = fn_ptr(sized_abstract, ..χ)
Or maybe there's another solution I'm missing.
Lang team meeting: an RFC is needed prior to stabilization, but we're happy to accept experimentatal implementation in the meantime.
@arielb1 Is there any progress on an object-safe implementation of this?
(sized_abstract, vtable) = coerce_sized self
fn_ptr = vtable[INDEX]
ret = fn_ptr(sized_abstract, ..χ)
I'm not sure I fully understand the syntax here but it seems like you also want to pass self to fn_ptr. This way the function can access the full self (the containing Rc as well as the contained value). I'm also not sure why the CoerceSized trait is required to be honest. I would imagine something like this:
trait MyTrait {
fn foo(self: &Rc<Self>, arg: String);
}
struct Thing;
impl MyTrait for Thing {
fn foo(self: &Rc<Self>, arg: String) {
unimplemented!()
}
}
Would create a vtable with Fn(&Rc<MyTrait>, &Thing, String). The &Self parameter is required because the user will want access to Rc<MyThing> as if it was a Rc<Self> but that relies on Rc returning the same type as it did last time you called .deref() which I don't think can be guaranteed. Passing a &Self parameter gives the user a reference with the correct type but now the signature in the trait and impl look different which is likely confusing.
So a "correct" impl signature would look something like this, but it is kinda ugly.
impl MyTrait for Thing {
fn foo(self: &Rc<MyTrait>, inner_self: &Self, arg: String) {
unimplemented!()
}
}
Also I have been really sloppy about throwing & around for the types, I guess just adding a &Self parameter and leaving the "actual" self parameter should work if it is self: &Foo<Self> but it won't be as easy if it is a value or &mut parameter as you won't be allowed to have that aliasing.
@kevincox
Not really. Someone needs to work on that.
The diagnostic seems improved so I'm closing in favor of https://github.com/rust-lang/rust/issues/44874.
Most helpful comment
Some notes from the @rust-lang/lang meeting:
Self".self: Rc<Self>.If someone is interested in pursuing it, we'd be happy to work with them (feel free to ping me), but it's not the highest priority at the moment.