pub trait Index<Idx>
where
Idx: ?Sized,
{
type Output: ?Sized;
fn index(&self, index: Idx) -> &Self::Output;
}
(06/14/20: to address any confusion, only unsized case is discussed here. sized case is a no brainer)
parameter index can be unsized local.
I wonder if rust internally uses unsized local a lot? Unsized local is widely used in the doc but it's not allowed for the user. So it's hard to read the doc because this contradicts to what we are told to use (trait object or slice).
Index::index looks quite weird. This seems to use function overloading. Is it equivalent to index.index(&**self)? Do Rustaceans have any preference or recommendations?
struct A {}
trait Foo {
fn method_by_ref(&self);
}
impl Foo for A {
fn method_by_ref(&self) { println!("a"); }
}
pub fn main() {
let a = A{};
Foo::method_by_ref(&a);
// is equivalent to
a.method_by_ref();
}
Is there any official name for the fully qualified form (the first one)?
Trait implementations of std::sync::Mutex seem to have similar trait bounds.
For example, Mutex has impl<T: ?Sized + Default> Default for Mutex<T> but obviously Default::default() cannot return unsized values (at least for now).
pub trait Index<Idx>
where
Idx: ?Sized,
{
type Output: ?Sized;
fn index(&self, index: Idx) -> &Self::Output;
}
is not an unsized local. It is a generic parameter with no restrictions on what types can be used in its place, so we might have Idx = usize, which would certainly not be an unsized local. Sure, the where-bounds don't disallow unsized types, but you will get another compiler error if you try.
It is likely done because if something is not allowed for one reason, there's no reason to use several methods at the same time to disallow it.
This is not really function overloading but rather just generics + traits. One could think of Index::index as coming from a definition like this:
mod Index {
fn index<I, T: Index<I>>(this: &T, ix: I) -> &T::Output { ... }
}
except for that T and I cannot actually be given as type parameters to Index::index like Index::index::<I,T>(...) but you would have to write <T as Index<I>>::index(...), and another difference is that you cannot do use Index::index. (Something that bugs me even more about Default::default(). You could however define a wrapper like
Which naturally leads me to answering your main question (something I didn’t even plan to do): Like @alice already explained, the way that the trait Index is defined doesn’t need unsized locals (you can easily define the same kind of trait yourself). But as you can see here, it doesn’t stand in the way of unsized locals either. Toy example of using an Index impl with unsized locals.
The formal term for this is "Universal Function Call Syntax" or "Disambiguating Function Calls", and The Reference explains how it works, plus provides a link to the RFC that introduced it.
You don't normally see it too often because people naturally try to avoid naming collisions. However, it's useful when you need to unambiguously say you want to invoke that method when writing code which might be used with arbitrary other traits and types in scope (mostly macros).
Kind of understand what this reply says: it's forward compatible with unsized local.
I just wonder, it's quite confusing since stable build doesn't allow unsized type to be passed by value, should I interpret this behavior when reading stable rust as ignoring unsized part when T: ?Sized being passed as value instead of reference?
The trait itself allows unsized values, but when you actually try to implement it without #![feature(unsized_locals)] the compiler will give an error. Stable rust doesn't ignore the unsized part of T: ?Sized. For example struct Box<T: ?Sized>(*mut T); does allow Box<[u8]> on stable despite T being unsized. It is just when the unsized T is used without being behind a reference that the compiler says no.
Yeah, that' what I'm saying. When passing T: ?Sized as value, ignore the unsized part and T must be sized for current rust with out unsized local enabled (to be more precise).
Or understand it as T can be any type and compiler will restrict it depending on compiler settings or flags.
[Written before the previous responder finished, so I’m repeating stuff]
I think you need to differentiate between trait definition and implementation. When the trait is defined and a method declaration looks like it might be getting an T: ?Sized as an argument, the compiler doesn’t care. If you however try to provide a corresponding trait implementation where your actual code tries to handle a generic T: ?Sized as an argument, then it will complain.
I made up a dialogue about the situation.
Coder: “I’m defining a protocol (trait), someone who follows it will generate money.”
Compiler: “Sure, I’ll remember this protocol.”
Coder: “I’m registering this money-generating procedure so I need you to check that it’s legal and also check that it follows the money-generating protocol I defined. Here’s how it goes: I’ll just generate money (by which I mean, I’ll just print the bank notes myself).”
Compiler: “Wait, that’s illegal!”
Well, the thing is, it's a nightly-only feature. So it's not allowed by the stable compiler, but it has to be documented somewhere (otherwise how would users of nightly Rust know what to look for and where?)
Thanks! Oh well, the learning curve of Rust is not that low compared with C++. It took almost all useful and commonly used features from C++. If learning curve for C++ is 10, then rust will be 8...
Nice dialog! Thanks a lot! Good clarification on declaration and definition for generic. That answers my misunderstanding too that the compiler is smart enough to check illegal usages on the declaration for generic. The check for generic only happens during monomorphization (at least for now).
For any non-generic declaration, it will error out at declaration time. See playground.
To clarify. you should use trait type by relying on trait object (sized) indirectly, instead of passing unsized trait typed value around without delegating to the fat pointers.
With that being said, you need to use a reference (fat pointer) for trait. But for unsized local, you don't have to obey that rule. Hope I make my words clear.
compiles throughout just about any rust version (from 1.0.0 on), which probably includes versions that are older than any unsized local feature. This demonstrates that it is not any case of special rules for forward compatibility with trait objects.
For your code perhaps using the now standard notation with the dyn could help
pub fn bar(foo: dyn Foo) {
foo.method();
}
In this case the compiler does now the type, it is a “trait object for the trait Foo”. (And the type dyn Foo is unsized, hence the error message when trying to pass it as a parameter).
Regarding the question of what’s a definition and what not: Everything that has a function body is a definition.