Share: 

1 January 0001

  • package # == git repo. The Cargo.toml file names the package
    • crate # This is the miniumum compilation unit. 0-1 libraries & 0+ binaries per package.
      • module # Per file!

Modules / Cargo.toml src/ main.rs # Module main foo.rs # Module foo foo/ barry.rs # Module foo::barry bar/ mod.rs # Module bar (older style, depricated) baz.rs # Module bar::baz

Crates - Simple / Cargo.toml src/main.rs # Crate root of a binary crate, eponymous with the Package src/lib.rs # Crate root of the library crate, eponymous with the Package

Crates - Complex / Cargo.toml src/ lib.rs # Crate root of the library Crate, eponymous with the Package bin/ foo/ main.rs # Crate root of a binary Crate called “foo”

Crates

  • Are “binary” or “library”.
    • Binary crates have a main function, in Module main

Compilation

  • Compilation starts at the “crate root”, which is a module aka file
  • This must explicitly pull in everything that it’s going to use, ie walk all the modules
    • mod foo imports an immediate child
    • Can only import direct children
    • Special case: Crate root looks “sideways” ie /main.rs “mod foo” looks in .foo.rs, whereas /foo.rs “mod bar” looks in /foo/bar.rs
  • All dependancies (specified in Cargo.toml) are implicity mod foo’d into the crate root, so the compiler finds them
    • Technically I think they’re compiled separately and available at link-time
    • You used to have to extern crate foo them from your crate root, but no more
    • The Project’s library Crate is also available as if it were a names dependancy
    • For a project myproj with the simple layout of src/[main,lib].rs, modules imported by main address:
      • Other stuff under main as crate:foo
      • Stuff under lib as myproj::bar

Visibility

  • Separate from using mod to walk the compiler through all the files
  • Names for things are paths
    • Obj if it’s local to this module
    • foo::bar::Obj - relative path from this module
      • self::foo::bar::Obj - seems redundant; same as above?
      • super::lol::foo::bar::Obj - .. equivalent. You can super::super::..., or when you get bored give give an absolute path starting crate::
        • super is, uh, super powerful, cause it cause it lets parent access child (importing with mod) and child access parent (via super) - in golang this would cause an import loop.
    • crate::lol:foo::bar::Obj - absolute path from the crate root
    • flibble::one::two::Three - absolute path from another crate’s root. Thus module names in your create can’t clash with external crate names
      • Another crate in this package, or a dependancy
  • use pulls names into the current scope
    • use foo::bar::Obj lets you say Obj as if it were local
    • use foo::bar::Obj as Lol - alias the thing
  • pub use re-exports the symbol as if it were declared in that module.
    • Can keep the module private (ie un-importable) with mod and then pub use bits of it.
    • Can be used to rearrange an API into a public face (or presumably several)

Access / Privacy

  • Things (fns, types, etc) must be marked pub to be usable by parent or sibling modules, including different packages that depend on your package
    • private stuff is usable by its own module and decendants
      • THINK! Your intuition is probably backwards. For a and a::b, b can use stuff in a (because it’s “more abstract/more public”); a cannot use stuff in b unless explicity marked pub (because they’re lower-level impl details)
    • pub means visible to everyone, including other crates that link you. This is your public API.
    • pub(crate) means visible within the current crate (binary or library), same as go’s internal/ I guess.
      • Useful for eg global helpers, but you can also achieve this by having them as top-level siblings of the crate root, meaning everything below them can use them
    • pub(super) visible to everything below, and one level above. Useful for eg the impls of a trait you define
  • pub mod re-exports the module. If you say mod foo, that importing module can access foo’s (public) members, but things that import the importer can’t.
    • note that child modules can access all things in their parents, even private stuff. The compiler’s even smart enough to let you do this if you refer to an ancestor with an absolute path.
  • Structs: individual fields have visibility (like they do in golang)
    • But there’s no default values like in golang, so if there’s even one private field, external modules can’t instantiate the struct cause they can’t give a value for that field - you’ll need a pub fn for that (or derive(Default))
    • Having a private field with a public getter fn but no setter fn is how you make things read-only? (orthogonal to mut bindings)
  • Enums: the enum is either public or private; no separate visibility on variants (members).
  • Traits: all members (fns, types) share visibility with the Trait

Conventions

  • One library, one or more binaries using its functions
  • Bring types (structs etc) right into the local scope, eg use foo::bar::Baz; Baz{}
  • Don’t bring functions into the local scope, just their parent modules (eg use foo::bar; bar::format_baz())

Using the names

Note that Cargo.toml, /src/bin/ dirs, etc, use a “pretty” version of the name, eg “foo-bar”. This is foo_bar as a symbol in code. Often you’ll want pretty-print versions the name of the current binary, and maybe the project it’s part of, to output. You’ll also want their code-symbols to eg set tracing levels for them. This gets fiddly when pretty names have - in them, which translates to _ in some places. The best way I’ve found to do this is:

/src/lib.rs

pub static LIB_CODE_NAME: &str = env!("CARGO_CRATE_NAME");

/src/bin/one-two/main.rs

pub static PROJ_PRETTY_NAME: &str = env!("CARGO_PKG_NAME");
pub static PROJ_VERSION: &str = env!("CARGO_PKG_VERSION");
pub static BIN_PRETTY_NAME: &str = env!("CARGO_BIN_NAME");
pub static BIN_CODE_NAME: &str = env!("CARGO_CRATE_NAME");

fn main() {
    println!("Welcome to {}, a command of {} {}", BIN_PRETTY_NAME, PROJ_PRETTY_NAME, PROJ_VERSION);
    tracing_subscriber::registry()
        ...
        .with_default(Level::INFO)
        .with_target(foo_bar::LIB_CODE_NAME, Level::DEBUG) // this package's library crate
        .with_target(BIN_CODE_NAME, Level::DEBUG) // this binary crate

Analogies

Golang

  • Package -> git repo
  • Crate -> “binary” ie a main package, or the single implict library in pkg
  • Module -> package, but in rust they’re files not directories
  • pub fn foo -> func Foo / fn foo -> func foo
  • mod foo -> import internal/foo / pub mod foo -> import foo