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

Evolution Across Phases

aspect-rs was developed in three phases, each building on the previous with increasing power and automation. This chapter compares the phases and explains the evolution.

Phase Overview

PhaseStatusApproachAnnotationAutomation
Phase 1✅ CompleteProc macros#[aspect] requiredManual
Phase 2✅ CompletePointcuts + Registry#[advice] optionalSemi-automatic
Phase 3✅ CompleteMIR weavingNone requiredFully automatic

Phase 1: Basic Infrastructure

Goal

Establish foundational AOP capabilities in Rust with minimal complexity.

Features

  • Core trait: Aspect trait with before/after/around advice
  • JoinPoint: Execution context with metadata
  • Proc macro: #[aspect(Expr)] attribute
  • Manual application: Explicit annotation on each function

Example

#![allow(unused)]
fn main() {
use aspect_core::prelude::*;
use aspect_macros::aspect;

struct Logger;

impl Aspect for Logger {
    fn before(&self, ctx: &JoinPoint) {
        println!("[ENTRY] {}", ctx.function_name);
    }
}

// Manual annotation required
#[aspect(Logger)]
fn fetch_user(id: u64) -> User {
    database::get(id)
}

#[aspect(Logger)]  // Must repeat for each function
fn save_user(user: User) -> Result<()> {
    database::save(user)
}
}

Strengths

  • Simple: Easy to understand and implement
  • Explicit: Clear what functions have aspects
  • Zero dependencies: aspect-core has no dependencies
  • Fast compilation: Minimal code generation

Limitations

  • Repetitive: Must annotate every function
  • Error-prone: Easy to forget annotations
  • Not scalable: Tedious for large codebases
  • Limited patterns: Can’t apply based on patterns

Implementation

Crates: 3

  • aspect-core (traits)
  • aspect-macros (#[aspect])
  • aspect-std (standard aspects)

Lines of Code: ~4,000 Tests: 108 Build Time: ~15 seconds

Phase 2: Production-Ready

Goal

Add declarative aspect application with pointcut patterns and global registry.

Features

  • Pointcut expressions: Pattern matching for functions
  • Global registry: Centralized aspect management
  • #[advice] macro: Register aspects with pointcuts
  • Boolean combinators: AND, OR, NOT for pointcuts
  • Aspect ordering: Control execution order

Example

#![allow(unused)]
fn main() {
use aspect_macros::advice;

// Register aspect with pointcut pattern
#[advice(
    pointcut = "execution(pub fn *(..)) && within(crate::api)",
    advice = "around",
    order = 10
)]
fn api_logger(pjp: ProceedingJoinPoint)
    -> Result<Box<dyn Any>, AspectError>
{
    println!("[API] {}", pjp.context().function_name);
    pjp.proceed()
}

// No annotations needed on functions!
pub fn fetch_user(id: u64) -> User {
    database::get(id)  // Automatically gets logging
}

pub fn save_user(user: User) -> Result<()> {
    database::save(user)  // Automatically gets logging
}
}

Pointcut Patterns

#![allow(unused)]
fn main() {
// Match all public functions
execution(pub fn *(..))

// Match functions in api module
within(crate::api)

// Match functions with specific names
name(fetch_* | save_*)

// Combine with boolean logic
execution(pub fn *(..)) && within(crate::api) && !name(test_*)
}

Strengths

  • Declarative: Define once, apply everywhere
  • Pattern-based: Flexible matching rules
  • Composable: Multiple aspects with ordering
  • Maintainable: Easy to add/remove aspects

Limitations

  • Still needs registration: Must use #[advice] somewhere
  • Compile-time only: Can’t change aspects at runtime
  • Limited to function-level: Can’t intercept field access

Implementation

Crates: 5

  • Previous 3 crates
  • aspect-pointcut (pattern matching)
  • aspect-runtime (global registry)

Lines of Code: ~6,000 Tests: 142 Build Time: ~25 seconds

Phase 3: Automatic Weaving

Goal

Achieve AspectJ-style automatic weaving with zero annotations via rustc integration.

Features

  • MIR analysis: Extract functions from compiled code
  • Automatic matching: Apply pointcuts without annotations
  • rustc integration: Custom compiler driver
  • Zero annotations: Completely annotation-free
  • Command-line config: Aspects configured via CLI

Example

#![allow(unused)]
fn main() {
// User code - NO ANNOTATIONS AT ALL!
pub fn fetch_user(id: u64) -> User {
    database::get(id)
}

pub fn save_user(user: User) -> Result<()> {
    database::save(user)
}

fn internal_helper() -> i32 {
    42
}
}

Compilation:

# Apply logging to all public functions
aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-type "LoggingAspect" \
    src/main.rs --crate-type lib

Result:

✅ Extracted 3 functions from MIR
✅ Matched 2 public functions:
   - fetch_user
   - save_user
✅ Applied LoggingAspect automatically

6-Step Pipeline

  1. Parse CLI: Extract pointcut expressions from command line
  2. Configure compiler: Set up custom rustc callbacks
  3. Access TyCtxt: Get compiler’s type context
  4. Extract MIR: Analyze mid-level IR for function metadata
  5. Match pointcuts: Apply pattern matching automatically
  6. Generate code: Weave aspects without annotations

Strengths

  • Zero boilerplate: No annotations in code
  • Centralized config: All aspects in one place
  • Impossible to forget: Can’t miss applying aspects
  • True AOP: Matches AspectJ capabilities
  • Still zero-cost: Compile-time weaving preserved

Limitations

  • Requires nightly: Uses unstable rustc APIs
  • Complex build: Custom compiler driver
  • Longer compilation: MIR analysis adds time

Implementation

Crates: 7

  • Previous 5 crates
  • aspect-weaver (advanced code generation)
  • aspect-rustc-driver (rustc integration)

Lines of Code: ~9,100 Tests: 194 Build Time: ~70 seconds (including rustc)

Comparison Matrix

Feature Comparison

FeaturePhase 1Phase 2Phase 3
Annotation required✅ Always⚠️ Optional❌ Never
Pointcut patterns
Global registry
Aspect ordering⚠️ Via nesting✅ Explicit✅ Explicit
MIR analysis
Automatic matching⚠️ Semi✅ Full
Compile-time only
Zero overhead
Stable Rust❌ Nightly
Build timeFastMediumSlower
Learning curveLowMediumMedium

Use Case Recommendations

Choose Phase 1 when:

  • Learning AOP in Rust
  • Small codebase (<1000 functions)
  • Explicit control desired
  • Stable Rust required
  • Fast iteration needed

Choose Phase 2 when:

  • Medium/large codebase
  • Pattern-based application desired
  • Multiple aspects needed
  • Aspect ordering important
  • Stable Rust required

Choose Phase 3 when:

  • Annotation-free code desired
  • Maximum automation needed
  • Large existing codebase
  • Nightly Rust acceptable
  • Production deployment (after testing)

Migration Path

Phase 1 → Phase 2

Before (Phase 1):

#![allow(unused)]
fn main() {
#[aspect(Logger)]
fn fetch_user(id: u64) -> User { ... }

#[aspect(Logger)]
fn save_user(user: User) -> Result<()> { ... }

#[aspect(Logger)]
fn delete_user(id: u64) -> Result<()> { ... }
}

After (Phase 2):

#![allow(unused)]
fn main() {
// Register once
#[advice(
    pointcut = "execution(pub fn *_user(..))",
    advice = "around"
)]
fn user_logger(pjp: ProceedingJoinPoint) { ... }

// Functions are automatically matched
fn fetch_user(id: u64) -> User { ... }
fn save_user(user: User) -> Result<()> { ... }
fn delete_user(id: u64) -> Result<()> { ... }
}

Benefits:

  • 67% less boilerplate (1 annotation vs 3)
  • Centralized aspect management
  • Easier to modify aspect rules

Phase 2 → Phase 3

Before (Phase 2):

#![allow(unused)]
fn main() {
#[advice(pointcut = "execution(pub fn *(..))", ...)]
fn logger(pjp: ProceedingJoinPoint) { ... }

pub fn fetch_user(id: u64) -> User { ... }
}

After (Phase 3):

#![allow(unused)]
fn main() {
// Code remains unchanged - no annotations!
pub fn fetch_user(id: u64) -> User { ... }
}

Build command:

# Instead of: cargo build
aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-type "LoggingAspect" \
    main.rs

Benefits:

  • 100% annotation-free
  • Build configuration instead of code annotations
  • Can change aspects without touching code

Timeline

Development

PhaseDurationEffortMilestone
Phase 1Weeks 1-44,000 LOCBasic AOP working
Phase 2Weeks 5-8+2,000 LOCPointcuts working
Phase 3Weeks 9-14+3,100 LOCMIR weaving complete

Total: 14 weeks, 9,100 lines of code, 194 tests

Testing

PhaseTestsCoverageStatus
Phase 110885%✅ All passing
Phase 214282%✅ All passing
Phase 319478%✅ All passing

Performance Across Phases

MetricPhase 1Phase 2Phase 3
No-op aspect overhead0ns0ns0ns
Simple aspect overhead~2%~2%~2%
Code size increase~5%~8%~8%
Compile time increase+10%+25%+50%
Runtime overhead0%0%0%

All phases achieve zero runtime overhead!

Architectural Evolution

Phase 1 Architecture

User Code
    ↓
#[aspect] macro
    ↓
Code generation
    ↓
Compiled binary

Simple linear flow.

Phase 2 Architecture

User Code (#[advice] registrations)
    ↓
aspect-runtime registry
    ↓
#[aspect] macro OR automatic weaving
    ↓
Pointcut matching
    ↓
Code generation
    ↓
Compiled binary

Added registry and pattern matching.

Phase 3 Architecture

User Code (no annotations)
    ↓
aspect-rustc-driver
    ↓
rustc compilation
    ↓
MIR extraction
    ↓
Pointcut matching
    ↓
Automatic code weaving
    ↓
Optimized binary

Fully integrated with compiler.

Future Phases

Potential Phase 4: Runtime AOP

Concept: Dynamic aspect application

Features:

  • Load aspects at runtime
  • Modify aspects without recompilation
  • JIT aspect weaving
  • Hot-reload aspects

Challenges:

  • Runtime overhead inevitable
  • Type safety harder to guarantee
  • Performance impact

Status: Research stage

See Also