use

use brings names into scope from a package or a file. Everything after from names a module; a path brings in a case of a type or a member another package attaches to it, and a use without names is an error.

A file starts with no names but the ones the prelude already brought in. use adds more of them, one declaration per statement, and where they come from is always written next to them.

Example#

use File as Files from "std/fs"
use * as console from "std/console"

fn areaOfCircle(radius: Float64): Float64 {
  Float.pi * radius ** 2
}

console.print areaOfCircle(2.0)
print Files.exists("project.trb")

Syntax#

use <Name> from "<owner>/<name>"                    a package: its src/lib.trb
use <Name> from "<owner>/<name>/<path>"             a module of a package: its src/<path>.trb
use <Name> from "./<path>"                          a relative import: a file of this package, no extension
use * as <alias> from "<module>"                    a namespace import
use <Name> as <Alias> from "<module>"               any name may take a local alias
use <Type>.<Case> from "<module>"                   a case, imported through its type
use <Type>.<member> from "<module>"                 a member an `extend` of that package adds to the type
use <Type>.<member> as <name> from "<module>"       ...under a name of this file's choosing
use <Type>.<Case>                                   without from: resolved in this file's own scope
public use <Name> from "<module>"                   a re-export

Rules#

  1. After from there is always a module. A path starting with ./ or ../ is a file, written without its .trb extension; every other path starts with a package name. "owner/name" is that package's src/lib.trb, "owner/name/path" is its src/path.trb. A program's entry is no module: use of src/main.trb, of "owner/name/main" or of the entry of a program line is an error, because a file that may hold top-level code is never importable.

  2. A relative path stays inside its package. It may leave src/ - a package is its whole directory, so tools/migrate.trb reaches "../src/cart" - but a ../ that climbs out of the package, or into a nested package below it, is an error that names the package: another package is imported by its name, "owner/name/path", and it has to be a dependency.

    use Vector2 from "../../linear/src/vector2"
  3. Only public declarations can be imported from another package, and only from a package that project.trb lists as a dependency. A name that is private to its file, that does not exist, or that is reachable only through a type it belongs to is not directly exported by the module.

    Does not compile
    use Cats from "std/core"
    // error: `Cats` is not exported by `std/core`
  4. A path brings in a case of the type, or a member another package attaches to it with an extend. A method, a constant or a field of the type's own body is never imported: it stays Type.member at every use. Naming something the type does not have is an error either way.

    Does not compile
    use Option.Maybe from "std/core"
    // error: `Option` has no case `Maybe`
    use Int64.seconds from "std/time"
    
    print 2.seconds()

    seconds is a member std/time adds to Int64, which std/number owns - so the file that calls it names where it comes from. See extend for the rule and for what needs no import at all. The type in front of the dot is a name of this file (Int64 comes from the prelude, not from std/time), and the from names the package the member has to come from:

    Does not compile
    use Int64.minutes from "std/time"
    // error: `std/time` adds no member `minutes` to `Int64`
  5. as gives an import a local name of its own, a case included. From that line on the local name is the only one the file has: it is what shadows an outer name, what a second use of the same name collides with, and what a "did you mean" note offers, and it is also the name a pattern matches the case with.

    use Option.Some as Present from "std/core"
    
    const value = Present 3
    print value
    
    fn describe(value: Int?): String {
      match value {
        Present(found) => "there is {found}"
        _ => "nothing"
      }
    }
  6. Without from, the path is resolved in the file's own scope. use Shape.Circle is what a file that declares Shape itself writes to bring its own case into scope the same way an importer would.

  7. There is no use Option.* and no brace group. Every name a file uses is written out, one per item of the list after use, so a dependency gaining a case is never by itself a reason this file changes.

  8. A use always names what it imports. Nothing runs when a module is imported, so a use with a path and no names would mean nothing at all:

    Does not compile
    use "std/text"
    // error: A `use` names what it imports
  9. public use re-exports, members included. public use Stack, ArrayStack from "./collections/stack" makes both names part of this file's own public surface, under their own name or, with as, under a new one; and public use Int64.seconds from "std/time" hands the member on, which is what the prelude does with it.

What this is not#

use is not a namespace you can dot into for everything a package has. Every name a file reads has to be written after use, once; there is no std.fs.File-style path to a name nobody imported.

use File from "std/fs"

print File.exists("project.trb")
Does not compile
print std.fs.File.exists("project.trb")
// error: Cannot find `std` here
  • Visibility - what has to be public before use can reach it.
  • The prelude - the names already in scope before the first use.
  • Packages - what "owner/name" after from names.
  • Cases and match - why an imported case is written bare in a pattern.
Esc