Why Do I Need To "Use" Things Sometimes That I'm Not Really Using In Rust?

196 Views Asked by At

Consider this code:

use chrono::{Local, NaiveDate};

fn main() {
    let d = Local::now().naive_local().date();
    println!("{}", d.num_days_from_ce());
}

This fails to compile with the strange error, "no method named num_days_from_ce found for struct NaiveDate in the current scope".

What? No method name? It's right there in the documentation‽

It took me a while to figure out I needed to add Datelike to the things I'm using from chrono. So my question is, why? Why force the consumers to have to import dependencies manually that we aren't using directly? This is not the only case of these necessary phantom imports in Rust...

Maybe there is something about Rust that I am not understanding here, or maybe there is something that can be changed about this library so that consumers don't need to use DateLike in this situation.

Also, why can't the compiler recommend WHAT to bring into the current scope?

Another issue with this syntax is that it produces a compiler warning that these necessary phantom imports are unused. Thus, there is no possible way for my code to compile cleanly...

warning: unused imports: `Datelike`, `NaiveDate`, `Weekday`
  --> src/bin/foo/bar.rs:25:18
   |
25 |     use chrono::{Datelike, Duration, Local, NaiveDate, Weekday};
   |                  ^^^^^^^^                   ^^^^^^^^^  ^^^^^^^
1

There are 1 best solutions below

6
drewtato On

Rust has a rule that you cannot use trait methods unless the trait is in scope. If the trait is not in scope, you can still use the functions, just not with the postfix method syntax.

use chrono::Local;

fn main() {
    let d = Local::now().naive_local().date();
    println!("{}", chrono::Datelike::num_days_from_ce(&d));
}

But for nearly all cases, you'll bring the trait in scope and use the method syntax. Many of the standard library traits, such as Iterator, are imported automatically.

This rule exists for API reasons. Anyone can implement a trait on a foreign type, so if two of your dependencies each have a trait with a method with the same name, it creates an ambiguity. Dependencies don't have an ordering, so picking one over the other would be ambiguous. If this caused an error, it would mean a crate implementing a trait could be a breaking API change. Instead, rust requires the trait to be in scope, which avoids these problems.

This also allows easily calling trait methods when the actual type has a method of the same name. For example, if a type has a method called borrow and also implements the Borrow trait, doing value.borrow() will call the type's method, but when Borrow is in scope, it will call Borrow::borrow.