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};
| ^^^^^^^^ ^^^^^^^^^ ^^^^^^^
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.
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
borrowand also implements theBorrowtrait, doingvalue.borrow()will call the type's method, but whenBorrowis in scope, it will callBorrow::borrow.