7

In the Solidity docs and also in OZ libaries, I mainly find this way of importing :

import "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";

But in most of the top projects I study on GitHub (PartyDao, Fractional, Aavegotchi etc.), I see this version :

import {
    ERC721HolderUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";

I do guess that in the second example we can only import specific contracts/interfaces or variable types from the imported filed. But as in 99% of the time you just have one contract or variable type inside the imported file, I wonder why it is still done this way by the devs. Is it for the code clarity ? Because I don't see any difference if you use the first or second version when you have only one object to import anyway from this file, like with the above example using ERC721HolderUpgradeable.

1 Answer 1

8

The import "X" form is fine in simple cases but has several downsides:

  1. You have no control over what is imported. It always imports all top-level definitions: contracts, structs, enums, events, functions, etc.
  2. You cannot avoid naming conflicts at all. You just cannot do import "X"; import "Y"; when both X and Y define the same name - even if the importing module does not use that name.
  3. It's harder to discover where a particular name is defined. Especially when your file has many imports and you're not viewing the code in an IDE. If you import every name explicitly, they works as "breadcrumbs" that can lead you to the place of definition with just a simple search through the file.
  4. Consistency / style enforcement. If in some cases you do need the longer form and it's not a huge burden, why not always use it? It's much easier to enforce a consistent style and automate it with tools when rules are simple and have no exceptions.
  5. Projects grow. There might be one contract there now but will it always stay that way?

This is not limited to just Solidity. The import syntax is modeled after JavaScript's import. It's also very similar to import in Python, where from X import * (a wildcard import) is a direct equivalent. In that community this form is frowned upon to the point that it's even mentioned in the main style guide for the language (PEP8 > imports):

Wildcard imports (from <module> import *) should be avoided, as they make it unclear which names are present in the namespace, confusing both readers and many automated tools. There is one defensible use case for a wildcard import, which is to republish an internal interface as part of a public API (for example, overwriting a pure Python implementation of an interface with the definitions from an optional accelerator module and exactly which definitions will be overwritten isn't known in advance).

You'll also find the same rule enforced by some analysis tools in these languages. Here's for example the reasoning from SonarSource:

  • SonarSource > JavaScript > "Wildcard imports should not be used"

    On the principle that clearer code is better code, you should explicitly import the things you want to use in a module. Using import * imports everything in the module, and runs the risk of confusing maintainers.

  • SonarSource > Python > "Wildcard imports should not be used"

    Importing every public name from a module using a wildcard (from mymodule import *) is a bad idea because:

    • It could lead to conflicts between names defined locally and the ones imported.
    • It reduces code readability as developers will have a hard time knowing where names come from.
    • It clutters the local namespace, which makes debugging more difficult.

    Remember that imported names can change when you update your dependencies. A wildcard import which works today might be broken tomorrow.

As for why the import "X" form is used by specific projects:

  • Solidity docs actually have very few examples using imports and where they do, the simpler form is used mostly to keep them concise.
  • In case of OpenZeppelin I'm not entirely sure, but keep in mind that the library has a long history. OpenZeppelin 1.0 was released in November 2016, while this feature only appeared in the compiler in January of that year. It might just not have been all that popular or even well known at the time. I generally see newer projects opting for more explicit imports.

The import {A, B, C} from "X" form is actually not your only choice. Solidity allows also import "X" as X, which may be more convenient in some cases. This way you do not have to import every name individually. Instead, names from module X when used need to be prefixed with X., which makes it clear where they come from.

Finally, remember that these are all just recommendations, not hard rules. All these forms still exist in the language for a reason. After considering all pros and cons it's ultimately your decision which one to use and when.

1
  • Amazing answer, thank you so much Cameel ! Commented Dec 23, 2021 at 15:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.