Names and scopes form the foundation of Rust's namespace system. Understanding how Rust resolves names, manages visibility, and organizes code into hierarchical namespaces is essential for writing well-structured Rust programs.
Rust's naming system provides fine-grained control over visibility and organization while maintaining memory safety and preventing name conflicts through its sophisticated module system.
Identifiers
Identifiers are names used to refer to various entities in Rust code, including variables, functions, types, modules, and other items.
// Valid identifiers
let variable_name = 42;
let _private = "hidden";
let CamelCase = true;
let snake_case_function = || {};
// Unicode identifiers are allowed
let 变量 = "Chinese variable name";
let π = 3.14159;
// Raw identifiers (escape keywords)
let r#match = "using match as identifier";
let r#type = "using type as identifier";
// Lifetime identifiers
fn function_with_lifetime<'a>(param: &'a str) -> &'a str {
param
}
// Type parameter identifiers
struct GenericStruct {
field1: T,
field2: U,
}
Identifier Rules
- Start: Letter, underscore, or Unicode character
- Continue: Letters, digits, underscores, or Unicode
- Keywords: Cannot use reserved keywords (unless raw identifiers)
- Case Sensitive:
Variableandvariableare different
Identifier Pattern Matching
r#{keyword}
→
Escaped keyword as identifier
let r#match = 5;
→
Use 'match' keyword as variable name
Scopes and Lexical Structure
Scopes define where names are valid and accessible. Rust uses lexical scoping, meaning the scope of a name is determined by where it appears in the source code.
Variable Shadowing
fn demonstrate_shadowing() {
let x = 5;
println!("x = {}", x); // x = 5
let x = x + 1; // Shadows previous x
println!("x = {}", x); // x = 6
{
let x = x * 2; // Shadows in inner scope
println!("x = {}", x); // x = 12
}
println!("x = {}", x); // x = 6 (outer shadow)
// Can change type when shadowing
let x = "Hello"; // Now x is a string
println!("x = {}", x); // x = Hello
}
Namespaces
Rust organizes names into several distinct namespaces. Items in different namespaces can have the same name without conflict.
Rust Namespaces
- Value namespace: Functions, variables, constants
- Type namespace: Types, type aliases, trait names
- Macro namespace: Macros and procedural macros
- Lifetime namespace: Lifetime parameters
- Label namespace: Loop and block labels
// Different namespaces can have same names
struct Option; // Type namespace
const Option: i32 = 42; // Value namespace
macro_rules! Option { // Macro namespace
() => { "macro" };
}
fn example<'Option>() { // Lifetime namespace
let value = Option; // Refers to constant
let instance: Option = Option; // Type and constant
let result = Option!(); // Macro
// Label namespace
'Option: loop {
break 'Option;
}
}
Module System and Visibility
Rust's module system provides hierarchical namespaces with fine-grained visibility control.
// Module definitions and visibility
mod outer_module {
// Private by default
fn private_function() {
println!("Private function");
}
// Explicitly public
pub fn public_function() {
println!("Public function");
private_function(); // Can access private items in same module
}
pub mod inner_module {
pub fn inner_public() {
// Can access parent's private items
super::private_function();
}
// Visible only to parent module
pub(super) fn visible_to_parent() {
println!("Visible to parent");
}
// Visible within entire crate
pub(crate) fn crate_visible() {
println!("Visible to entire crate");
}
// Visible only in this module and descendants
pub(in crate::outer_module) fn restricted_visibility() {
println!("Restricted visibility");
}
}
}
// Usage
fn main() {
outer_module::public_function();
outer_module::inner_module::inner_public();
// These would be compilation errors:
// outer_module::private_function(); // Private
// outer_module::inner_module::visible_to_parent(); // Not visible here
}
Name Resolution
Rust follows specific rules for name resolution - determining what entity a name refers to in a given context.
Resolution Order
- Built-in types: Primitive types like
i32,bool - Local scope: Variables and parameters in current scope
- Item scope: Items in current module
- Prelude: Standard library prelude items
- Extern crate: External crate root modules
use std::collections::HashMap;
// Module-level items
const GLOBAL_CONSTANT: i32 = 100;
fn name_resolution_example() {
let local_var = 42;
// Refers to local variable
println!("{}", local_var);
// Refers to module-level constant
println!("{}", GLOBAL_CONSTANT);
// HashMap from std::collections via use statement
let mut map = HashMap::new();
map.insert("key", "value");
// Fully qualified syntax
let vec = std::vec::Vec::::new();
// Absolute path from crate root
let result = crate::name_resolution_example;
}
// Disambiguation with fully qualified syntax
trait A {
fn method(&self);
}
trait B {
fn method(&self);
}
struct S;
impl A for S {
fn method(&self) {
println!("Method from trait A");
}
}
impl B for S {
fn method(&self) {
println!("Method from trait B");
}
}
fn disambiguation() {
let s = S;
// Ambiguous - compilation error
// s.method();
// Disambiguate using fully qualified syntax
A::method(&s);
B::method(&s);
// Alternative syntax
::method(&s);
::method(&s);
}
Use Statements and Imports
The use statement brings names from other modules into scope:
// Various forms of use statements
use std::collections::HashMap; // Single import
use std::collections::{HashSet, BTreeMap}; // Multiple imports
use std::collections::*; // Glob import (discouraged)
use std::io::Result as IoResult; // Rename import
use std::fs::{self, File}; // Import module and item
// Re-export for external users
pub use std::collections::HashMap as Map;
// Conditional imports
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
// Use in different scopes
fn scoped_use() {
use std::thread;
thread::spawn(|| {
println!("Thread work");
});
// thread is only available in this function
}
// Importing from current crate
mod my_module {
pub fn helper() {}
}
use crate::my_module::helper; // Absolute path
use self::my_module::helper as my_helper; // Relative path
Paths and Path Resolution
Paths specify the location of items within the module hierarchy:
// Different types of paths
mod parent {
pub mod child {
pub fn function() {}
pub mod grandchild {
pub fn deeply_nested() {
// Absolute path from crate root
crate::parent::child::function();
// Relative path to parent
super::function();
// Relative path to grandparent
super::super::child::function();
// Self reference
self::deeply_nested();
}
}
}
}
// External crate paths
use ::std::collections::HashMap; // Absolute from extern crate
// Path segments
// crate::module::submodule::item
// ::extern_crate::module::item
// super::sibling_module::item
// self::child_module::item
Advanced Naming Concepts
Associated Items
struct MyStruct;
impl MyStruct {
// Associated function (static)
fn new() -> Self {
MyStruct
}
// Associated constant
const CONSTANT: i32 = 42;
// Method (has self)
fn method(&self) {
println!("Method called");
}
}
// Accessing associated items
fn main() {
let instance = MyStruct::new();
instance.method();
println!("Constant: {}", MyStruct::CONSTANT);
// Fully qualified method call
MyStruct::method(&instance);
}
Trait Items and Disambiguation
trait Display {
fn display(&self);
}
trait Debug {
fn display(&self); // Same name as Display::display
}
struct Item;
impl Display for Item {
fn display(&self) {
println!("Display implementation");
}
}
impl Debug for Item {
fn display(&self) {
println!("Debug implementation");
}
}
fn disambiguation_example() {
let item = Item;
// Ambiguous without qualification
// item.display(); // Error: multiple applicable items
// Disambiguate using trait syntax
Display::display(&item);
Debug::display(&item);
// Fully qualified syntax
- ::display(&item);
- ::display(&item);
}
Best Practices
Naming Conventions
- snake_case: Variables, functions, modules
- SCREAMING_SNAKE_CASE: Constants, static variables
- CamelCase: Types, traits, enum variants
- Descriptive: Use clear, meaningful names
Visibility Guidelines
- Start with private and expose as needed
- Use
pub(crate)for internal APIs - Document public interfaces thoroughly
- Prefer smaller, focused modules
🤖 For AI Coding Agents
Name Resolution System Instructions
When analyzing names and scopes, follow these machine-readable specifications:
{
"name_resolution": {
"version": "1.0",
"namespaces": {
"value": {
"contains": ["functions", "variables", "constants", "statics"],
"resolution_order": ["local_scope", "item_scope", "prelude", "extern_crate"]
},
"type": {
"contains": ["structs", "enums", "traits", "type_aliases"],
"conflicts": "separate_from_value_namespace"
},
"macro": {
"contains": ["declarative_macros", "procedural_macros"],
"expansion_phase": "before_name_resolution"
},
"lifetime": {
"contains": ["lifetime_parameters"],
"scope": "function_or_impl_block"
},
"label": {
"contains": ["loop_labels", "block_labels"],
"scope": "local_to_function"
}
},
"visibility_modifiers": {
"private": "default - accessible only within current module",
"pub": "public to all external modules",
"pub(crate)": "visible throughout current crate only",
"pub(super)": "visible to parent module only",
"pub(in path)": "visible to specified module path only"
},
"path_resolution": {
"absolute": "crate::path::to::item",
"relative_parent": "super::sibling::item",
"relative_self": "self::child::item",
"extern_crate": "::external_crate::item"
}
}
}
Scope Analysis Templates
{ scope_level++; track_names(current_scope); }
→
Scope[2]: [outer_var, inner_var] → shadows: [outer_var]
{
"scope_tracking": {
"entry_patterns": ["{", "fn", "loop", "if", "match"],
"exit_patterns": ["}"],
"shadowing_rules": {
"allowed": "same_scope_level_or_inner",
"type_change": "permitted_with_let_rebinding",
"resolution": "innermost_scope_wins"
},
"name_lookup": {
"order": ["local_bindings", "function_parameters", "item_definitions", "use_imports", "prelude"],
"disambiguation": "fully_qualified_syntax_required_for_conflicts"
}
}
}
✅ Correct: Proper Name Resolution
// ✅ VALID: Clear scope hierarchy
fn proper_scoping() {
let outer = 10; // Scope level 0
{ // Scope level 1 begins
let inner = 20; // New binding in inner scope
let outer = 30; // Shadows outer, valid
println!("{} {}", outer, inner); // 30, 20
} // Scope level 1 ends
println!("{}", outer); // 10 (original outer)
// inner not accessible here - good scoping
}
// ✅ VALID: Module visibility control
mod parent {
pub fn public_function() {}
fn private_function() {}
pub mod child {
pub fn access_parent() {
super::private_function(); // Valid: child can access parent private
}
}
}
pub use parent::child::access_parent; // Valid re-export
❌ Incorrect: Name Resolution Errors
// ❌ INVALID: Use after scope ends
fn scope_error() {
{
let scoped_var = 42;
}
println!("{}", scoped_var); // ERROR: not in scope
}
// ❌ INVALID: Ambiguous trait method calls
trait TraitA {
fn method(&self);
}
trait TraitB {
fn method(&self);
}
struct S;
impl TraitA for S { fn method(&self) {} }
impl TraitB for S { fn method(&self) {} }
fn ambiguous_call() {
let s = S;
s.method(); // ERROR: ambiguous
// ✅ SOLUTION: Disambiguate
TraitA::method(&s);
::method(&s);
}
Namespace Separation Demo
Value Namespace
const Item: i32 = 42;
Type Namespace
struct Item { field: i32 }
Macro Namespace
macro_rules! Item { ... }