Confused by extern crate renaming in 2018 edition

Everything I've read about 2018 edition suggests that the following should not work, but it does:

// (for an easy-to-compile example, I picked a common crate
//   that has no dependencies)
extern crate dtoa as d;

mod foo {
    use d::write; // How is this line valid in 2018 edition?

    pub fn bar() {
        let mut buf = Vec::new();
        write(&mut buf, 2.71828f64).unwrap();
        println!("{:?}", buf);
    }
}

fn main() {
    foo::bar()
}

which part of 2018 edition states that this wouldn't work? looks fine to me (unless i missed something)

Yeah I don't understand why this wouldn't work anymore? 2018 edition made extern crate optional, but it didn't remove it.

@dylan.dpc @jethrogb

Consider the reason why it works in 2015 edition:

  • extern crate dtoa as d; creates a crate item at the path ::d.
  • use d::write is equivalent to use ::d::write.

Versus the 2018 edition:

  • extern crate dtoa as d; creates a crate item at the path crate::d.
  • use d::write is not equivalent to use crate::d::write. It searches the prelude for anything called d and finds nothing, and so it becomes equivalent to use self::d::write, which does not exist.

The edition guide even contains this quote:

If you've been using as to rename your crate like this:

extern crate futures as f;

use f::Future;

then removing the extern crate line on its own won't work. You'll need to do this:

use futures as f;

use self::f::Future;

This change will need to happen in any module that uses f .

(but note that even this quote is clearly outdated, because use self:: is unnecessary now that uniform paths has been merged)

No. Just as before, it still creates at at ::d. extern crate _ as _ is not equivalent to extern crate _ followed by a re-export. Your quote is about what you need to do when you do remove the extern crate item.

Regardless, the compiler does not look in :: to resolve names according to all of the discussion I have seen thus far; the behavior was discussed as follows:

  • (fundamental change of 2018 edition): The crate root is no longer implicitly searched during name resolution.
  • (stable feature extern_prelude): All crates provided by --extern flags to the compiler appear in the prelude.
  • (stable feature uniform_paths): self:: is implicitly searched during name resolution.

Clearly, this description is inadequate. d is not among the --extern flags, and is not visible in self. Yet it is available for name resolution inside submodules.


Furthermore, this seems pretty arbitrary. Consider what happens if the extern crate is moved into a module:

mod lol {
    extern crate dtoa as d;
    
    mod foo {
        use ::d::write;
    
        pub fn bar() {
            let mut buf = Vec::new();
            write(&mut buf, 2.71828f64).unwrap();
            println!("{:?}", buf);
        }
    }
    
    fn main() {
        foo::bar()
    }    
}

Now it stops compiling as it can no longer find d. So apparently, extern crate a as b; makes b available for name resolution everywhere if and only if it appears in the crate root. This is a pretty specific and complex rule to have added to our "simplified" import scheme!

1 Like

This was implemented in https://github.com/rust-lang/rust/pull/54658 and some of the use cases are explained there.

2 Likes

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