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!
- crate # This is the miniumum compilation unit. 0-1 libraries & 0+ binaries per package.
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 Modulemain
- Binary crates have a
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
- Other stuff under main as
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 cansuper::super::...
, or when you get bored give give an absolute path startingcrate::
super
is, uh, super powerful, cause it cause it lets parent access child (importing withmod
) and child access parent (viasuper
) - 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 scopeuse foo::bar::Obj
lets you sayObj
as if it were localuse 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 thenpub use
bits of it. - Can be used to rearrange an API into a public face (or presumably several)
- Can keep the module private (ie un-importable) with
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)
- 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
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’sinternal/
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
- private stuff is usable by its own module and decendants
pub mod
re-exports the module. If you saymod 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