When we use a type A that implements trait T, and trait T has method m(), we have to declare use T;
use T;
let a: A;
a.m();
I'm curious why we need to declare use T;
? Shouldn't rustc already know type A
implements m()
?
Thanks.
When we use a type A that implements trait T, and trait T has method m(), we have to declare use T;
use T;
let a: A;
a.m();
I'm curious why we need to declare use T;
? Shouldn't rustc already know type A
implements m()
?
Thanks.
Many types implement Debug and Display, both have the fmt
function. If you didn't have either in scope, Rust wouldn't know which one to call.
That's a good point. What if we import both traits , and call the method ? Which one will rust pick? (or how to explicitly enforce?)
You can use turbofish syntax to be completely explicit.
What do you think this does?
fn main() {
let vec = vec![1, 2, 3];
println!("{:?}", vec.first());
}
Usually it would print Some(1)
. But what if you one day added a new dependency that contains this somewhere?
trait VecExt {
fn first(&self) -> usize;
}
impl<T> VecExt for Vec<T> {
fn first(&self) -> usize {
self.len()
}
}
What now?
Rust would complain that the call is ambiguous, and would advise you to call it like this:
Debug::fmt(self, ...):
Thanks! I realized that I could have tried out such cases offline. Just did it in the playground and indeed it does that. I learned Rust for a few weeks and just know this now. Something new to learn everyday ..
It seems like some online pieces refer turbofish syntax
as ::<type>
, while in this case the solution would be <trait>::
. Are both referred as turbofish syntax? (maybe just fish is swimming in different direction ? )
One more thought: when there is no ambiguity, rustc should be able to compile without use <trait>
, right?
It's a minor thing, but it feels a bit odd when we import trait use T
but we don't have any reference to T
in the code as we are calling:
a.m()
Of course when there is ambiguity, it's clear that we need to import use T
because we need to write:
T::m(&a)
So my question now is: is there any downside not to require use T
if there is no ambiguity? (The upside is that code is a bit shorter and IMO less confusing).
This would mean that adding a new trait in a library could break unrelated code in projects that use that library (since it could introduce ambiguity where there was none before).
In my understanding, there are two cases:
Suppose the old trait is T1
, the new trait is T2
, both has method m()
. And suppose the unrelated code has struct type A
and instance a
.
T2
.then there is still no ambiguity for calling a.m()
even after adding T2
in the lib.
T2
as well.then there will be ambiguity and in any case we need to change the code. Yes, rustc can rely on use T1
without use T2
to pick m()
but in such case IMO it's better to write T1::m(&a)
anyway.
Just my 2cents.
One thing to consider is, that A
doesn't have to be changed at to implement T2
. Unlike interfaces in other programming languages, traits don't have to be specified when defining the type. New traits can be added to a type anywhere, even in different crates.
The trait T2
could be a some obscure helper trait that you don't care about, defined in some random library that you use.
For a concrete example, have a look at std::convert::From
implementors.
There are a lot of interesting conversions that these impls allow, but impls over generics are most relevant to the conversation. Consider this one:
impl<T> From<T> for Option<T>
This converts any type to an Option-wrapped type. Even your A
struct. Now suppose traits T1
and T2
were generic over T
, like this From<T>
example. As soon as either trait is brought into scope, your type implements it.
I understand the frustration, just have to live with it. To add syntax that would prioritise something (so not needing the use
by default) seems to add complication and conflict.
For anyone not wanting to be bullied by other languages it is
Universal Function Call Syntax. (UFCS)
When you include everything (type as trait, full paths, type parameters) it becomes Fully Qualified
.
::<Type>
) is distinct from this syntax (<Foo as Type>::
), as they do different things.This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.