Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

`use` isn't an import statement. #460

Closed
ahmedcharles opened this Issue Feb 21, 2017 · 6 comments

Comments

Projects
None yet
5 participants
@ahmedcharles
Copy link
Contributor

ahmedcharles commented Feb 21, 2017

There has been some recent discussion about the confusion people having with the difference between use, mod and extern crate. The guessing game tutorial contains:

By default, Rust imports only a few types into every program in the prelude. If a type you want to use isn’t in the prelude, you have to import that type into your program explicitly with a use statement. Using the std::io library provides you with a number of useful io-related features, including the functionality to accept user input.

It uses the word import, to refer to what the use statement does. However, the use statement doesn't actually 'import' things in the same sense that an #include in C++ would or a require in JS would. extern crate is the only way to import code external to the current crate. mod is also an import like statement, but Rust is unique in that it uses two keyword phrases to identify internal vs external imports. I suppose this is similar to the difference between #include "foo.h" and #include <foo.h> (which is that the version with " will search the current directory before other include paths).

Anyways, use introduces a new binding (in programming language theory parlance), which is a new name for something else which already exists. The tutorial glosses over the fact that use std::io; is only possible because there is an implicit extern crate std; which is the actual import statement. However, it's probably a good thing that the tutorial not mention that specifically. However, the distinction between what people consider 'import' to mean vs what use actually does is important and describing use std::io; as importing io is doing a disservice to users who then go on to get confused about mod and extern crate because those are the true import statements and they don't understand why use doesn't work instead.

@carols10cents

This comment has been minimized.

Copy link
Member

carols10cents commented Feb 21, 2017

What would be a way of describing what use std::io; does, that would be accurate and doesn't use programming language theory terms? In other places, we say "bring into scope", do you consider that to be more accurate?

As someone who isn't a PL researcher and is more of a tell-me-what-i-need-to-do-to-accomplish-my-goal sort of programmer, I don't have a precise definition of "import" in my mind, it's more like "using io::Read stuff in my code doesn't work until I add use std::io;" and I don't really have a particular name for that thing I have to do.

I don't want the book to be wrong, but I also don't want it to get hung up on terminology.

@ahmedcharles

This comment has been minimized.

Copy link
Contributor Author

ahmedcharles commented Feb 21, 2017

Merriam Webster defines import as: to bring from a foreign or external source. extern crate does that and mod does that, but use doesn't do that, even just thinking about a lay person's definition of import.

If you describe std::io::stdin() as being a function with the name stdin which is inside the module io, which is itself inside the module std, where std is in the current scope due to an implicit extern crate std;. Then, use std::io; allows one to use io to refer to the module referred to by std::io. That's obviously not a good way of describing it to non-programmers in a tutorial... though it would probably work later in the book.

I'd suggest changing the first example to be:

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    std::io::stdin().read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

Removing the use statement means that you can explain what the std::io::stdin syntax does (currently 'associated function of io' is used to explain what stdin is, but I've only ever heard of associated types in Rust and only then on traits, calling it a function within the module io seems more accurate and less confusing).

Then in the next example, std::io::stdin can be changed to use std::io; and io::stdin and use can be explained as a way to allow code to refer to io directly rather than having to prefix std:: every time.

As for the 'hung up on terminology' part, the only reason I looked at this is because there's a threat proposing changing the language because people are confused. And I'd much rather get hung up on terminology in documentation and tutorials than change the language. I hope everyone can agree on that.

@steveklabnik

This comment has been minimized.

Copy link
Member

steveklabnik commented Feb 22, 2017

Merriam Webster defines import as: to bring from a foreign or external source. extern crate does that and mod does that, but use doesn't do that, even just thinking about a lay person's definition of import.

Yes, use does do that. It brings a name into scope from a different scope. Also, many computer words aren't in the general dictionary ... so that's tough.

I'd suggest changing the first example to be:

This is non-idiomatic Rust though. I understand the desire to get rid of a concept, but I also really don't want to show off worse code because of it.

In general, the Guessing Game is meant to be a fast and loose intro. You should get the gist of things, but it doesn't go into a lot of depth. It's also the most well-trodden piece of text we have; it's the same as in the original book, where it was the intro there, and is actually in and of itself older than the book, IIRC. We don't even expect people to have read the Guessing Game, it's skippable if desired.

@projektir

This comment has been minimized.

Copy link
Contributor

projektir commented Feb 22, 2017

I think how a given person perceives the word "use" or "import" depends on their background. Coming from C# and JavaScript, I instantly associated "use" with "using", and didn't expect any literal importing. I think the usage of "import" in the Guessing Game is rather ambiguous and will likely mean different things to different people, potentially some of them incorrect, but that seems OK for an introductory program.

@ahmedcharles

This comment has been minimized.

Copy link
Contributor Author

ahmedcharles commented Feb 22, 2017

At a high level, I'd much rather sit on the fence when it comes to the content of tutorials. However, given that people find the module system in Rust to be confusing and are suggesting changes to the language which I find to be an extreme solution, I'm trying to improve the docs as a first attempt at remedying the situation. If the lang team is spending valuable time considering changes (which I think are misguided) to the language to make it easier to understand, I don't get why this level of resistance is being given to an attempt at improving the docs, in the name of "well, it's just introductory text, how could being more precise with the wording make any difference?"

Yes, use does do that. It brings a name into scope from a different scope. Also, many computer words aren't in the general dictionary ... so that's tough.

I suppose we could reinterpret the word import to mean anything either of us wanted. But if we look at functions, modules, types, scopes, etc as nodes in a graph with the start node being the current scope and the edges being names, then we have a mental model for how name lookup works. Follow edges based on their name until we reach the appropriate item from the current scope. This model allows us to consider 'foreign' things to be things we currently cannot reach, because there are no connections from the current scope that allows access to them. Importing would be the process of adding nodes to the graph along with edges, which would allow access. That is, it brings 'items' from a foreign source. However, use statements 'simply' create new edges, without adding new nodes and hence, do not allow access to things that were inaccessible before and do not add foreign things to the graph.

Now, this definition is pedantic and I don't expect a new programmer or user of Rust to read a tutorial with the word import and immediately think about name lookup and graph theory. However, words are important and picking the right ones to describe the appropriate concepts is also important. If import is used to describe what use does, how does one then describe what mod and extern crate do without confusing things? My point isn't really to be pedantic about whether use is an import or not, but that use when compared to mod and extern crates are different concepts.

This is non-idiomatic Rust though. I understand the desire to get rid of a concept, but I also really don't want to show off worse code because of it.

I personally use fully qualified names when initially writing code and only add use statements when it's obvious that I'm going to be using a particular name multiple times and it would improve readability.

In any case, I'm not suggesting it be removed anyways, I'm suggesting that use be taught by comparing and contrasting code which doesn't use it with code that does. That, combined with avoiding the use of the word import, should result in people having a better chance of intuitively learning what use does, because it becomes obvious that naming and not about which code is accessible to them.

In general, the Guessing Game is meant to be a fast and loose intro. You should get the gist of things, but it doesn't go into a lot of depth. It's also the most well-trodden piece of text we have; it's the same as in the original book, where it was the intro there, and is actually in and of itself older than the book, IIRC. We don't even expect people to have read the Guessing Game, it's skippable if desired.

Being fast and loose is dangerous though, because people can walk away with false first impressions. If you think of each chapter in the book as a compass, then they should all point in the same direction but be of different length (i.e. different depth of knowledge). And it being the most well-trodden could just explain why people have issues with learning the difference between use and mod. I'm not attempting to point fingers, but rather, pointing out that something is popular doesn't mean that it's correct. And the fact that it's skippable is irrelevant when considering it's correctness when not skipped.

@tshepang

This comment has been minimized.

Copy link
Contributor

tshepang commented Feb 22, 2017

I learned stuff while reading this, thanks @ahmedcharles. Also good points. We have to be careful with terminology.

I support the idea of showing an example of code with and without use. If that's too much for guessing game, rather use terms like bring into scope instead of import.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.