Module System
Use Statements
A module is a .fv file. A use statement imports items from another
module. Take a project with these files:
project/
├── main.fv
├── types.fv
├── components.fv
├── widgets/
│ └── controls.fv
└── utils/
└── helpers.fv
pub struct User { name: String, age: I32 }
impl User {
fn is_adult(self) -> Boolean { self.age >= limit() }
}
// Private: other modules cannot import it, but the module's own code
// still calls it.
fn limit() -> I32 { 18 }
pub struct Button { label: String }
pub struct Text { value: String }
pub struct VStack { gap: I32 }
pub struct Slider { value: I32 }
pub fn formatDate(day: I32) -> String { "day " }
main.fv imports from each of them:
// Import a single item
use types::User
// Import multiple items
use components::{Button, Text, VStack}
// Import from a nested path: widgets/controls.fv
use widgets::controls::Slider
use utils::helpers::formatDate
pub fn run_checks() {
let user = User(name: "Ada", age: 36)
assert(condition: user.is_adult()) // the impl block comes too
assert(condition: Button(label: "ok").label == "ok")
assert(condition: Slider(value: 3).value == 3)
assert(condition: formatDate(day: 1) == "day ")
}
Module Resolution:
- Modules map to
.fvfiles - Path separators use
::. The segments before the last one name the file:use widgets::controls::Sliderreadswidgets/controls.fv. The last segment, or each name in{ }, names an item of that file - A
useimports items. It does not import a module as a whole:use utils::helperslooks for an itemhelpersinutils.fv - A path that no file matches is the error
ModuleNotFound. A name that the file does not declare is the errorImportItemNotFound - Can only import
pubitems. A private item is the errorPrivateImport - No circular imports allowed: two modules that import each other are
the error
CircularImport
What an Import Brings
An import brings the item and what belongs to it:
- A struct or an enum comes with its impl blocks, so its methods work in the importer.
- A trait impl is part of its type. The importer can call the methods
of the impl with no
useof the trait. - A trait comes with its blanket impls. Each type of the importer that meets the bounds gets the trait: see Blanket Impls.
- A blanket impl of the importer is for each type of an imported module that meets its bounds, also a type that the importer does not import by name but gets as a value, as for a type of an inline module.
- Two module files can each declare a trait of one name. They stay two traits in the importer: a bound on one does not accept an impl of the other, and each call runs the impl of its own trait.
- The imported code keeps its own private helpers.
is_adultabove calls the privatelimit, and the importer cannot namelimit.
pub trait Area { fn area(self) -> I32 }
pub struct Square { side: I32 }
impl Area for Square {
fn area(self) -> I32 { self.side * self.side }
}
use geom::Square
pub fn run_checks() {
assert(condition: Square(side: 3).area() == 9) // no `use geom::Area`
}
Which module may write an impl
An impl of a trait is in the module that declares the trait or the
target type. This is the orphan rule. The prelude declares the
primitives. An impl in another module is the error OrphanImpl (E179).
So two modules can never give one type two impls of one trait. An impl
with no trait, impl String { ... }, has no such rule.
Re-exports
A plain use makes the name available inside the importing module
only. Another module cannot import the name through it. A pub use
exports the name again, so a module can collect items from several
files:
pub use types::User
pub use geom::Square
use prelude::{User, Square}
pub fn run_checks() {
assert(condition: Square(side: 2).area() == 4)
assert(condition: User(name: "Bo", age: 9).age == 9)
}
The module plain.fv imports Button with a plain use:
use components::Button
So another module cannot import Button from it:
use plain::Button
Nested Modules
Use mod blocks to create nested namespaces within a file:
mod alignment {
pub enum Vertical {
top,
center,
bottom
}
pub enum Horizontal {
left,
center,
right
}
}
// Use with namespace path
pub let vertical: alignment::Vertical = .top
pub let horizontal: alignment::Horizontal = .center
// Can also import nested items
use alignment::Vertical
pub let v: Vertical = .bottom
Items of a module are private unless they are pub. Code outside the
module reaches only the pub items, by path (alignment::Vertical)
or by use. A private item is the error VisibilityViolation when a
path names it, and PrivateImport when a use names it. A nested
module that is not pub is private too:
mod tools {
pub fn shown() -> I32 { hidden() } // ok: inside the module
fn hidden() -> I32 { 1 }
}
pub let a: I32 = tools::shown() // ok
pub let b: I32 = tools::hidden() // error: 'hidden' is private
An inline module and a module file can have the same name. Then a
use of that name has two meanings, and the compiler refuses it with
the error AmbiguousModulePath. This page has the file types.fv:
mod types {
pub struct User { name: String }
}
use types::User
Rename the inline module, or the file.
Multiple Levels:
mod ui {
pub mod layout {
pub enum Direction {
horizontal,
vertical
}
}
pub struct Theme {
primary: String,
secondary: String
}
}
pub let direction: ui::layout::Direction = .horizontal
pub let theme: ui::Theme = ui::Theme(
primary: "#007AFF",
secondary: "#5856D6"
)
Paths in a module, and traits by path:
A path in a module starts from that module first, then from each
module that holds it, then from the top level. In ui, the path
layout::Direction names ui::layout::Direction.
A trait of a module is one trait by each name that reaches it:
Show inside shapes, shapes::Show outside, and Show after
use shapes::Show. A bound and an impl header can name it by path:
mod shapes {
pub trait Show {
fn show(self) -> I32
}
}
pub struct Square { side: I32 }
impl shapes::Show for Square {
fn show(self) -> I32 { self.side }
}
fn shown<T: shapes::Show>(x: T) -> I32 { x.show() }
pub let s: I32 = shown(x: Square(side: 2))
A Public Signature Names Public Types
A pub definition is what another module sees. Naming a private type
in one hands the reader a value whose type they cannot write down: they
can call the function, but they cannot declare a binding for the
result, pass it on, or name it in their own signature.
struct Hidden { x: I32 }
pub fn f() -> Hidden { Hidden(x: 1) } // error E141
pub fn g(h: Hidden) -> I32 { h.x } // error E141
pub struct Shown { h: Hidden } // error E141
fn build() -> Hidden { Hidden(x: 1) } // ok: not public
The rule reaches inside containers, because [Hidden] and Hidden?
name the same type a bare Hidden does. It covers each part of a
public definition that another module sees: the parameters and the
return type of a function (an extern fn too), the fields of a
struct, the payload fields of an enum, the fields and methods of a
trait, the type of a let (written or inferred), a type argument, a
tuple field, a closure type, and a generic bound. The expression of
a default value is not part of the signature: it may use a private
type, if the value has a public type. The rule
holds inside a nested module too.
This pairs with the closure rule: a struct that holds a closure field
cannot be pub, so no public signature may name it either. Such a
struct is built and read inside its own module, and the module exposes
the results instead. See
Closures / Rules.