Why do we need to declare "use <trait>"

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?

1 Like

Rust would complain that the call is ambiguous, and would advise you to call it like this:

Debug::fmt(self, ...):
2 Likes

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 ? )

1 Like

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.

  1. If A implemented T1, and was not changed to implement new trait T2.

then there is still no ambiguity for calling a.m() even after adding T2 in the lib.

  1. If A implemented T1, and then changed to implement 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.

2 Likes
  1. Turbofish (::<Type>) is distinct from this syntax (<Foo as Type>::), as they do different things.
  2. It is not called UFCS in Rust. We did used to call it that (your link is to the docs for Rust 1.0) but it's not like UFCS in other languages. It's called qualified path syntax.
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.