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

What aspect-rs Can Do

This section provides a concrete overview of aspect-rs capabilities, limitations, and use cases.

Supported Features

✅ Four Advice Types

aspect-rs supports all common advice types:

AdviceWhen it runsUse cases
beforeBefore functionLogging, validation, authorization
afterAfter successLogging, metrics, cleanup
after_throwingOn error/panicError logging, alerting, rollback
aroundWraps executionTiming, caching, transactions, retry

✅ Function Types Supported

aspect-rs works with various function types:

#![allow(unused)]
fn main() {
// Regular functions
#[aspect(LoggingAspect::new())]
fn sync_function(x: i32) -> i32 { x * 2 }

// Async functions
#[aspect(LoggingAspect::new())]
async fn async_function(x: i32) -> i32 { x * 2 }

// Generic functions
#[aspect(LoggingAspect::new())]
fn generic_function<T: Display>(x: T) -> String {
    x.to_string()
}

// Functions with lifetimes
#[aspect(LoggingAspect::new())]
fn with_lifetime<'a>(s: &'a str) -> &'a str { s }

// Methods (associated functions)
impl MyStruct {
    #[aspect(LoggingAspect::new())]
    fn method(&self) -> i32 { self.value }
}

// Functions returning Result
#[aspect(LoggingAspect::new())]
fn returns_result(x: i32) -> Result<i32, Error> {
    Ok(x * 2)
}
}

✅ Multiple Aspects Composition

Stack multiple aspects on a single function:

#![allow(unused)]
fn main() {
#[aspect(LoggingAspect::new())]
#[aspect(TimingAspect::new())]
#[aspect(CachingAspect::new())]
#[aspect(MetricsAspect::new())]
fn fetch_user(id: u64) -> Result<User, Error> {
    database::query_user(id)
}
}

Execution order (outermost first):

  1. MetricsAspect::before()
  2. CachingAspect::around() → checks cache
  3. TimingAspect::around() → starts timer
  4. LoggingAspect::before()
  5. Function executes
  6. LoggingAspect::after()
  7. TimingAspect::around() → records time
  8. CachingAspect::around() → caches result
  9. MetricsAspect::after()

✅ Thread Safety

All aspects must implement Send + Sync:

#![allow(unused)]
fn main() {
pub trait Aspect: Send + Sync {
    // ...
}
}

This ensures aspects can be used safely across threads.

✅ Eight Standard Aspects

The aspect-std crate provides production-ready aspects:

#![allow(unused)]
fn main() {
use aspect_std::*;

// 1. Logging
#[aspect(LoggingAspect::new())]
fn process_order(order: Order) { ... }

// 2. Timing/Performance Monitoring
#[aspect(TimingAspect::new())]
fn expensive_calculation(n: u64) -> u64 { ... }

// 3. Caching/Memoization
#[aspect(CachingAspect::new())]
fn fibonacci(n: u64) -> u64 { ... }

// 4. Metrics Collection
#[aspect(MetricsAspect::new())]
fn api_endpoint() -> Response { ... }

// 5. Rate Limiting
#[aspect(RateLimitAspect::new(100, Duration::from_secs(60)))]
fn api_call() -> Response { ... }

// 6. Circuit Breaker
#[aspect(CircuitBreakerAspect::new(5, Duration::from_secs(30)))]
fn external_service() -> Result<Data, Error> { ... }

// 7. Authorization (RBAC)
#[aspect(AuthorizationAspect::require_role("admin", get_user_roles))]
fn delete_user(id: u64) -> Result<(), Error> { ... }

// 8. Validation
#[aspect(ValidationAspect::new())]
fn create_user(email: String) -> Result<User, Error> { ... }
}

✅ Zero Runtime Dependencies

The aspect-core crate has zero dependencies:

[dependencies]
# No runtime dependencies!

Generated code doesn’t depend on aspect-rs at runtime. The aspect logic is inlined at compile time.

✅ Low Overhead

Benchmarks show <10ns overhead for simple aspects:

Aspect TypeBaselineWith AspectOverhead
Empty function10ns12ns+2ns (20%)
Logging15ns17ns+2ns (13%)
Timing20ns22ns+2ns (10%)
Caching (hit)5ns7ns+2ns (40%)
Caching (miss)100ns102ns+2ns (2%)

The overhead is a constant ~2ns, not proportional to function complexity.

✅ Compile-Time Type Checking

All aspect code is type-checked:

#![allow(unused)]
fn main() {
#[aspect(LoggingAspect::new())]
fn returns_number() -> i32 { 42 }

impl Aspect for LoggingAspect {
    fn after(&self, ctx: &JoinPoint, result: &dyn Any) {
        // This would fail at compile time if types don't match
        if let Some(num) = result.downcast_ref::<i32>() {
            println!("Result: {}", num);
        }
    }
}
}

✅ Ownership and Lifetime Safety

Aspects respect Rust’s ownership rules:

#![allow(unused)]
fn main() {
#[aspect(LoggingAspect::new())]
fn takes_ownership(data: Vec<String>) -> Vec<String> {
    // 'data' is moved, not borrowed
    data.into_iter().map(|s| s.to_uppercase()).collect()
}

#[aspect(LoggingAspect::new())]
fn borrows_data(data: &[String]) -> usize {
    // 'data' is borrowed, not moved
    data.len()
}
}

The macro preserves the original function’s ownership semantics.

Current Limitations (Phase 1-2)

⚠️ Per-Function Annotation Required

Currently, you must annotate each function individually:

#![allow(unused)]
fn main() {
// Must annotate every function
#[aspect(LoggingAspect::new())]
fn function1() { }

#[aspect(LoggingAspect::new())]
fn function2() { }

#[aspect(LoggingAspect::new())]
fn function3() { }
}

Phase 3 will support pattern-based matching:

#![allow(unused)]
fn main() {
// Phase 3 - Annotate once, apply to all matching functions
#[advice(
    pointcut = "execution(pub fn *(..)) && within(crate::api)",
    advice = "before"
)]
static LOGGER: LoggingAspect = LoggingAspect::new();
}

⚠️ Function Execution Only

Currently, only function calls are join points. You cannot intercept:

  • Field access (obj.field)
  • Method calls at call-site (obj.method() vs method definition)
  • Static initialization
  • Exception handling

Phase 3+ will add these capabilities.

⚠️ Limited Pointcut Expressions

Phase 1-2 only supports direct aspect application. No pattern matching like:

#![allow(unused)]
fn main() {
// Not yet supported in Phase 1-2
pointcut = "execution(* create_*(..))"  // All functions starting with "create_"
pointcut = "@annotation(#[cached])"     // All functions with #[cached]
pointcut = "within(crate::api::*)"      // All functions in api module
}

Phase 3 will add full pointcut expression support.

⚠️ No Inter-Type Declarations

AspectJ allows adding fields/methods to existing types. aspect-rs does not support this.

// AspectJ - Can add fields to existing classes
aspect LoggingAspect {
    private int UserService.callCount;  // Add field

    public void UserService.logCalls() {  // Add method
        System.out.println(this.callCount);
    }
}

Not planned for aspect-rs (violates Rust’s encapsulation).

Use Cases

✅ Ideal Use Cases

aspect-rs excels at:

  1. Logging & Observability

    • Entry/exit logging
    • Distributed tracing correlation IDs
    • Structured logging with context
  2. Performance Monitoring

    • Execution time measurement
    • Slow function warnings
    • Performance regression detection
  3. Caching

    • Memoization of expensive computations
    • Cache invalidation strategies
    • Cache hit/miss metrics
  4. Security & Authorization

    • Role-based access control (RBAC)
    • Authentication checks
    • Audit logging
  5. Resilience Patterns

    • Circuit breakers
    • Retry logic
    • Timeouts
    • Rate limiting
  6. Metrics & Analytics

    • Call counters
    • Latency percentiles
    • Error rates
    • Business metrics
  7. Transaction Management

    • Database transaction boundaries
    • Rollback on error
    • Nested transactions
  8. Validation

    • Input validation
    • Precondition checks
    • Invariant verification

⚠️ Not Ideal Use Cases

aspect-rs is not the best choice for:

  1. One-off functionality - Just write manual code
  2. HTTP-specific middleware - Use framework middleware (Actix, Tower)
  3. Runtime-swappable behavior - Use trait objects or strategy pattern
  4. Bytecode manipulation - Not possible in Rust
  5. Extreme zero-dependency requirements - Even though aspect-core has zero deps, you still need the macro at compile time

Feature Comparison

FeaturePhase 1 (MVP)Phase 2 (Production)Phase 3 (Automatic)
Basic macro weaving
Four advice types
Async support
Generic functions
Standard aspects⚠️ Basic✅ 8 aspects✅ 8+ aspects
Per-function annotation✅ Required✅ Required⚠️ Optional
Pointcut expressions⚠️ Limited✅ Full
Pattern matching
Call-site interception✅ Planned
Field access✅ Planned

Summary

What aspect-rs does well:

  • ✅ Zero-cost abstraction (<10ns overhead)
  • ✅ Compile-time type safety
  • ✅ Production-ready standard aspects
  • ✅ Async and generic function support
  • ✅ Clean separation of concerns

Current limitations (to be addressed in Phase 3):

  • ⚠️ Per-function annotations required
  • ⚠️ Function execution only (no field access yet)
  • ⚠️ Limited pointcut expressions

Not in scope:

  • ❌ Runtime aspect swapping
  • ❌ Bytecode manipulation
  • ❌ Inter-type declarations

Next Steps

Ready to try aspect-rs? Continue to Chapter 3: Getting Started for a 5-minute quickstart!

Want to understand the implementation? Jump to Chapter 6: Architecture.