Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pointcut Expressions

This chapter explains the pointcut expression language used for automatic aspect matching, including syntax, semantics, and advanced patterns.

What Are Pointcuts?

Pointcuts are expressions that select join points (function calls) where aspects should be applied:

Pointcut Expression → Selects Functions → Aspects Applied

Example:

--aspect-pointcut "execution(pub fn *(..))"
# Selects all public functions

Pointcut Syntax

Execution Pointcut

Matches function execution based on signature:

execution(VISIBILITY fn NAME(PARAMETERS))

Components:

  • VISIBILITY: pub, pub(crate), or omit for any
  • fn: Keyword (required)
  • NAME: Function name or * wildcard
  • PARAMETERS: (..) for any parameters

Examples:

# All public functions
execution(pub fn *(..))

# Specific function
execution(pub fn fetch_user(..))

# All functions (any visibility)
execution(fn *(..))

# Private functions
execution(fn *(..) where !pub)

Within Pointcut

Matches functions within a specific module:

within(MODULE_PATH)

Examples:

# All functions in api module
within(api)

# Nested module
within(api::handlers)

# Full path
within(crate::api)

Call Pointcut

Matches function calls (caller perspective):

call(FUNCTION_NAME)

Examples:

# Any call to database::query
call(database::query)

# Any call to functions starting with "fetch_"
call(fetch_*)

Pattern Matching

Wildcard Matching

Use * to match any name:

# All functions
execution(fn *(..))

# All functions starting with "get_"
execution(fn get_*(..))

# All functions in any submodule
within(*::handlers)

Visibility Matching

# Public functions
execution(pub fn *(..))

# Crate-visible functions
execution(pub(crate) fn *(..))

# Private functions (no visibility keyword)
execution(fn *(..) where !pub)

Module Path Matching

# Exact module
within(api)

# Module prefix
within(api::*)

# Nested modules
within(crate::api::handlers)

Implementation Details

Pointcut Data Structure

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, PartialEq)]
pub enum Pointcut {
    Execution(ExecutionPattern),
    Within(String),
    Call(String),
    And(Box<Pointcut>, Box<Pointcut>),
    Or(Box<Pointcut>, Box<Pointcut>),
    Not(Box<Pointcut>),
}

#[derive(Debug, Clone, PartialEq)]
pub struct ExecutionPattern {
    pub visibility: Option<VisibilityKind>,
    pub name: String,          // "*" for wildcard
    pub async_fn: Option<bool>,
}
}

Parsing Execution Patterns

#![allow(unused)]
fn main() {
impl Pointcut {
    pub fn parse_execution(expr: &str) -> Result<Self, ParseError> {
        // expr = "execution(pub fn *(..))
        
        // Remove "execution(" and trailing ")"
        let inner = expr
            .strip_prefix("execution(")
            .and_then(|s| s.strip_suffix(")"))
            .ok_or(ParseError::InvalidSyntax)?;
        
        // Parse: "pub fn *(..)"
        let parts: Vec<&str> = inner.split_whitespace().collect();
        
        let mut visibility = None;
        let mut name = "*".to_string();
        let mut async_fn = None;
        
        let mut i = 0;
        
        // Check for visibility
        if parts.get(i) == Some(&"pub") {
            visibility = Some(VisibilityKind::Public);
            i += 1;
            
            // Check for pub(crate)
            if parts.get(i).map(|s| s.starts_with("(crate)")).unwrap_or(false) {
                visibility = Some(VisibilityKind::Crate);
                i += 1;
            }
        }
        
        // Check for async
        if parts.get(i) == Some(&"async") {
            async_fn = Some(true);
            i += 1;
        }
        
        // Expect "fn"
        if parts.get(i) != Some(&"fn") {
            return Err(ParseError::MissingFnKeyword);
        }
        i += 1;
        
        // Get function name
        if let Some(name_part) = parts.get(i) {
            // Remove trailing "(..)" if present
            name = name_part.trim_end_matches("(..)").to_string();
        }
        
        Ok(Pointcut::Execution(ExecutionPattern {
            visibility,
            name,
            async_fn,
        }))
    }
}
}

Matching Algorithm

#![allow(unused)]
fn main() {
impl PointcutMatcher {
    pub fn matches(&self, pointcut: &Pointcut, func: &FunctionMetadata) -> bool {
        match pointcut {
            Pointcut::Execution(pattern) => {
                self.matches_execution(pattern, func)
            }
            Pointcut::Within(module) => {
                self.matches_within(module, func)
            }
            Pointcut::Call(name) => {
                self.matches_call(name, func)
            }
            Pointcut::And(p1, p2) => {
                self.matches(p1, func) && self.matches(p2, func)
            }
            Pointcut::Or(p1, p2) => {
                self.matches(p1, func) || self.matches(p2, func)
            }
            Pointcut::Not(p) => {
                !self.matches(p, func)
            }
        }
    }
    
    fn matches_execution(
        &self,
        pattern: &ExecutionPattern,
        func: &FunctionMetadata
    ) -> bool {
        // Check visibility
        if let Some(required_vis) = &pattern.visibility {
            if &func.visibility != required_vis {
                return false;
            }
        }
        
        // Check async
        if let Some(required_async) = pattern.async_fn {
            if func.is_async != required_async {
                return false;
            }
        }
        
        // Check name
        if pattern.name != "*" {
            if !self.matches_name(&pattern.name, &func.simple_name) {
                return false;
            }
        }
        
        true
    }
    
    fn matches_name(&self, pattern: &str, name: &str) -> bool {
        if pattern == "*" {
            return true;
        }
        
        // Wildcard matching
        if pattern.ends_with("*") {
            let prefix = pattern.trim_end_matches('*');
            return name.starts_with(prefix);
        }
        
        if pattern.starts_with("*") {
            let suffix = pattern.trim_start_matches('*');
            return name.ends_with(suffix);
        }
        
        // Exact match
        pattern == name
    }
    
    fn matches_within(&self, module: &str, func: &FunctionMetadata) -> bool {
        // Check if function is within specified module
        
        // Handle wildcard
        if module.ends_with("::*") {
            let prefix = module.trim_end_matches("::*");
            return func.module_path.starts_with(prefix);
        }
        
        // Exact match or prefix match
        func.module_path == module ||
        func.module_path.starts_with(&format!("{}::", module)) ||
        func.qualified_name.contains(&format!("::{}", module))
    }
}
}

Boolean Combinators

Combine pointcuts with logical operators:

AND Combinator

# Public functions in api module
execution(pub fn *(..)) && within(api)

Implementation:

#![allow(unused)]
fn main() {
Pointcut::And(
    Box::new(Pointcut::Execution(/* pub fn *(..) */)),
    Box::new(Pointcut::Within("api".to_string())),
)
}

OR Combinator

# Functions in api or handlers modules
within(api) || within(handlers)

Implementation:

#![allow(unused)]
fn main() {
Pointcut::Or(
    Box::new(Pointcut::Within("api".to_string())),
    Box::new(Pointcut::Within("handlers".to_string())),
)
}

NOT Combinator

# All functions except in tests module
execution(fn *(..)) && !within(tests)

Implementation:

#![allow(unused)]
fn main() {
Pointcut::And(
    Box::new(Pointcut::Execution(/* fn *(..) */)),
    Box::new(Pointcut::Not(
        Box::new(Pointcut::Within("tests".to_string())),
    )),
)
}

Practical Examples

Example 1: API Logging

Apply logging to all public API functions:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..)) && within(api)" \
    --aspect-apply "LoggingAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub mod api {
    pub fn fetch_user(id: u64) -> User { }
    pub fn save_user(user: User) -> Result<()> { }
}

// ✗ Not matched (not in api module)
pub fn helper() { }

// ✗ Not matched (private)
mod api {
    fn internal() { }
}
}

Example 2: Async Function Timing

Time all async functions:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub async fn *(..))" \
    --aspect-apply "TimingAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub async fn fetch_data() -> Data { }

// ✗ Not matched (not async)
pub fn sync_function() { }

// ✗ Not matched (private)
async fn private_async() { }
}

Example 3: Database Transaction Management

Apply transactions to all database operations:

aspect-rustc-driver \
    --aspect-pointcut "within(database::ops)" \
    --aspect-apply "TransactionalAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
mod database {
    mod ops {
        pub fn insert(data: Data) -> Result<()> { }
        fn delete(id: u64) -> Result<()> { }  // Also matched
    }
}

// ✗ Not matched
mod database {
    pub fn connect() -> Connection { }
}
}

Example 4: Security for Admin Functions

Apply authorization to admin functions:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn admin_*(..))" \
    --aspect-apply "AuthorizationAspect::require_role(\"admin\")"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub fn admin_delete_user(id: u64) { }
pub fn admin_grant_permissions(user: User) { }

// ✗ Not matched
pub fn user_profile() { }
pub fn admin() { }  // Exact match, not prefix
}

Example 5: Exclude Test Code

Apply aspects to all code except tests:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..)) && !within(tests)" \
    --aspect-apply "MetricsAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub fn production_code() { }

mod api {
    pub fn handler() { }  // ✓ Matched
}

// ✗ Not matched
mod tests {
    pub fn test_something() { }
}
}

Command-Line Usage

Single Pointcut

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    main.rs

Multiple Pointcuts

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-pointcut "within(api)" \
    --aspect-pointcut "within(handlers)" \
    main.rs

Each pointcut is evaluated independently.

With Aspect Application

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-apply "LoggingAspect::new()" \
    main.rs

Configuration File

Create aspect-config.toml:

[[pointcuts]]
pattern = "execution(pub fn *(..))"
aspects = ["LoggingAspect::new()"]

[[pointcuts]]
pattern = "within(api)"
aspects = ["TimingAspect::new()", "SecurityAspect::new()"]

[options]
verbose = true
output = "target/aspect-analysis.txt"

Use with:

aspect-rustc-driver --aspect-config aspect-config.toml main.rs

Advanced Patterns

Combining Multiple Criteria

# Public async functions in api module, except tests
execution(pub async fn *(..)) && within(api) && !within(api::tests)

Prefix and Suffix Matching

# Functions starting with "get_"
execution(fn get_*(..))

# Functions ending with "_handler"
execution(fn *_handler(..))

Module Hierarchies

# All submodules of api
within(api::*)

# Specific nested module
within(crate::services::api::handlers)

Visibility Variants

# Public and crate-visible
execution(pub fn *(..)) || execution(pub(crate) fn *(..))

# Only truly public
execution(pub fn *(..)) && !execution(pub(crate) fn *(..))

Pointcut Library

Common pointcut patterns for reuse:

All Public API

--aspect-pointcut "execution(pub fn *(..)) && (within(api) || within(handlers))"

All Database Operations

--aspect-pointcut "within(database) || call(query) || call(execute)"

All HTTP Handlers

--aspect-pointcut "execution(pub async fn *_handler(..))"

All Admin Functions

--aspect-pointcut "execution(pub fn admin_*(..)) || within(admin)"

Production Code Only

--aspect-pointcut "execution(fn *(..)) && !within(tests) && !within(benches)"

Performance Considerations

Pointcut Evaluation Cost

#![allow(unused)]
fn main() {
// Fast: Simple checks
execution(pub fn *(..))          // O(1) visibility check

// Medium: String matching
execution(fn get_*(..))          // O(n) prefix check

// Slow: Complex combinators
(execution(...) && within(...)) || (!execution(...))  // Multiple checks
}

Optimization strategy:

  • Evaluate cheapest checks first
  • Short-circuit on failure
  • Cache results when possible

Compilation Impact

Simple pointcut:    +1% compile time
Complex pointcut:   +3% compile time
Multiple pointcuts: +2% per pointcut

Still negligible compared to total compilation.

Testing Pointcuts

Dry Run Mode

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-dry-run \
    --aspect-output matches.txt \
    main.rs

Outputs matched functions without applying aspects.

Verification

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_execution_pointcut() {
        let pointcut = Pointcut::parse_execution("execution(pub fn *(..))").unwrap();
        let func = FunctionMetadata {
            simple_name: "test_func".to_string(),
            visibility: VisibilityKind::Public,
            // ...
        };
        
        let matcher = PointcutMatcher::new(vec![pointcut]);
        assert!(matcher.matches(&pointcut, &func));
    }
    
    #[test]
    fn test_within_pointcut() {
        let pointcut = Pointcut::Within("api".to_string());
        let func = FunctionMetadata {
            module_path: "crate::api".to_string(),
            // ...
        };
        
        let matcher = PointcutMatcher::new(vec![pointcut]);
        assert!(matcher.matches(&pointcut, &func));
    }
}
}

Error Handling

Invalid Syntax

$ aspect-rustc-driver --aspect-pointcut "invalid syntax"

error: Failed to parse pointcut expression
  --> invalid syntax
   |
   | Expected: execution(PATTERN) or within(MODULE)

Missing Components

$ aspect-rustc-driver --aspect-pointcut "execution(*(..))

error: Missing 'fn' keyword in execution pointcut
  --> execution(*(..))
   |
   | Expected: execution([pub] fn NAME(..))

Unsupported Features

$ aspect-rustc-driver --aspect-pointcut "args(i32, String)"

error: 'args' pointcut not yet supported
  --> Use execution(...) or within(...) instead

Future Enhancements

Planned Features

  1. Parameter Matching

    execution(fn *(id: u64, ..))
    
  2. Return Type Matching

    execution(fn *(..) -> Result<T, E>)
    
  3. Annotation Matching

    execution(@deprecated fn *(..))
    
  4. Call-Site Matching

    call(database::query) && within(api)
    
  5. Field Access

    get(User.email) || set(User.*)
    

Key Takeaways

  1. Pointcuts select functions automatically - No manual annotations
  2. Three main types - execution, within, call
  3. Wildcards enable flexible matching - * matches anything
  4. Boolean combinators - AND, OR, NOT for complex logic
  5. Compile-time evaluation - Zero runtime cost
  6. Extensible design - Easy to add new pointcut types
  7. Production-ready - Handles real Rust code

Next Steps


Related Chapters: